后台-分类管理

Filter配合Servlet

1. 一个路径对应一个Servlet的弊端

通过观察:http://127.0.0.1:8080/tmall/admin_category_list

可以发现,各类需要:增加,删除,编辑,修改,查询5个服务端功能。按照传统的在web.xml中配置Servlet的方式,一个路径对应一个Servlet的思路,就需要设计5个Servlet类,并且在wen.xml中配置5个路径:

  • AddCategoryServlet
  • DeleteCategoryServlet
  • EditCategoryServlet
  • UpdateCategoryServlet
  • ListCateagoryServlet

而后台需要的分类,产品,属性,产品图片,用户,订单这么6种管理,那么就一共需要30个Servlet,以及在web.xml中对应的配置,那么配置文件就会变得臃肿,并且容易出错。

2. 对设计进行修改

观察代码,发现Servlet轴一个即CategoryServlet,web.xml里也只有CategoryServlet映射,并没有5个映射。

 

对设计进行改进

3.原理流程图

CategoryServlet的list()方法会被调用

  • 1.假设访问路径是 http://127.0.0.1:8080/tmall/admin_category_list
  • 2. 过滤器BackServletFilter进行拦截,判断访问的地址是否以/admin_开头
  • 3. 如果是,那么做如下操作
      3.1 取出两个下划线之间的值 category
      3.2 取出最后一个下划线之后的值 list
      3.3 然后根据这个值,服务端跳转到categoryServlet,并且把list这个值传递过去
  • 4. categoryServlet 继承了BaseBackServlet,其service方法会被调用。 在service中,借助反射技术,根据传递过来的值 list,调用对应categoryServlet 中的方法list()
  • 5. 这样就实现了当访问的路径是 admin_category_list的时候,就会调用categoryServlet.list()方法这样一个效果
     

换句话说:
如果访问的路径是admin_category_add,就会调用categoryServlet.add()方法
如果访问的路径是admin_category_delete,就会调用categoryServlet.delete()方法
如果访问的路径是admin_category_edit,就会调用categoryServlet.edit()方法
如果访问的路径是admin_category_update,就会调用categoryServlet.update()方法
如此这般,一个categoryServlet类,就完成了本来需要5个Servlet类才能完成的功能。

原理流程图

4. 代码讲解-BackServletFilter

4.1首先贼web.xml配置文件中,让所有的请求都会经过BackServletFilter

<url-pattern>/*</url-pattern>

4.2 还是假设访问的路径是:

http://127.0.0.1:8080/tmall/admin_category_list

4.3 在BackServletFilter中通过request.getRequestURI()访问取出的uri:/tmall/admin_category_list

4.4 然后截断/tmall,得到路径/admin_category_list

4.5 判断是否以/admin开头

4.6 如果是,那么就取出两个_之间的字符串,category,并且拼接成/categoryServlet,通过服务端跳转到/categoryServlet

4.7 在跳转之前,还取出了list字符串,然后通过request.setAttrbute的方式,借助服务端跳转,传递到categoryServlet里去。

package tmall.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

public class BackServletFilter implements Filter {

	public void destroy() {
		
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		
		String contextPath=request.getServletContext().getContextPath();
		String uri = request.getRequestURI();
		uri =StringUtils.remove(uri, contextPath);
		if(uri.startsWith("/admin_")){		
			String servletPath = StringUtils.substringBetween(uri,"_", "_") + "Servlet";
			String method = StringUtils.substringAfterLast(uri,"_" );
			request.setAttribute("method", method);
			req.getRequestDispatcher("/" + servletPath).forward(request, response);
			return;
		}
		
		chain.doFilter(request, response);
	}

	public void init(FilterConfig arg0) throws ServletException {
	
	}
}
    <filter>
        <filter-name>BackServletFilter</filter-name>
        <filter-class>tmall.filter.BackServletFilter</filter-class>
    </filter>
 
    <filter-mapping>
        <filter-name>BackServletFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

5. 代码讲解

流程就到categoryServlet,根据web.xml中的配置:

<servlet-name>CategoryServlet</servlet-name>
<url-pattern>/categoryServlet</url-pattern>

服务端跳转/categoryServlet就到了CategoryServlet这个类里

  • 1. 首先CategoryServlet继承了BaseBackServlet,而BaseBackServlet又继承了HttpServlet
  • 2. 服务端跳转过来之后,会访问CategoryServlet的doGet()或者doPost()方法
  • 3. 在访问doGet()或者doPost()之前,会访问service()方法
  • 4. BaseBackServlet中重写了service() 方法,所以流程就进入到了service()中
  • 5. 在service()方法中有三块内容
      5.1 第一块是获取分页信息
      5.2 第二块是根据反射访问对应的方法
      5.3 第三块是根据对应方法的返回值,进行服务端跳转、客户端跳转、或者直接输出字符串。
  • 6. 第一块和第三块放在后面讲解,这里着重讲解第二块是根据反射访问对应的方法
    6.1 取到从BackServletFilter中request.setAttribute()传递过来的值 list
    6.2 根据这个值list,借助反射机制调用CategoryServlet类中的list()方法

6. 一个Servlet类就能满足CRUD一系列业务要求

通过这样一种模式,一个Servlet类就能满足CRUD一系列业务要求
如果访问的路径是admin_category_list,就会调用categoryServlet.list()方法
如果访问的路径是admin_category_add,就会调用categoryServlet.add()方法
如果访问的路径是admin_category_delete,就会调用categoryServlet.delete()方法
如果访问的路径是admin_category_edit,就会调用categoryServlet.edit()方法
如果访问的路径是admin_category_update,就会调用categoryServlet.update()方法

一个Servlet类就能满足CRUD一系列业务要求

JSP包含关系

1. 分类查询对应的JSP文件

分类查询对应的JSP文件时listCategory.jsp。 

listCategory.jsp用到了4个公共包含文件

  • 1. <%@include file="../include/admin/adminHeader.jsp"%>
  • 2. <%@include file="../include/admin/adminNavigator.jsp"%>
  • 3. <%@include file="../include/admin/adminPage.jsp"%>
  • 4. <%@include file="../include/admin/adminFooter.jsp"%>

分类查询对应的JSP文件

2. adminHeader.jsp

每个后台页面都在一开始使用了adminHeader.jsp

2.1 表示本页面使用HTML5的技术

<!DOCTYPE html>

2.2 jsp指令

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" isELIgnored="false"%>

contentType="text/html;charset=UTF-8"告诉浏览器使用UTF-8进行中文编码识别

pageEncoding="UTF-8"本jsp上的中文文字,使用UTF-8进行编码

isELIgnored="false"本jsp上会使用EL表达式

2.3 引入JSTL

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix='fmt' %> 

使用c和fmt两种标准标签库

2.4 引入js和css

<script src="js/jquery/2.0.0/jquery.min.js"></script>
<link href="css/bootstrap/3.3.6/bootstrap.min.css" rel="stylesheet">
<script src="js/bootstrap/3.3.6/bootstrap.min.js"></script>
<link href="css/back/style.css" rel="stylesheet">

2.5 预先定义一些判断输入框的函数,方便后面使用

<script>
function checkEmpty(id, name){
	var value = $("#"+id).val();
	if(value.length==0){
		alert(name+ "不能为空");
		$("#"+id)[0].focus();
		return false;
	}
	return true;
}
function checkNumber(id, name){
	var value = $("#"+id).val();
	if(value.length==0){
		alert(name+ "不能为空");
		$("#"+id)[0].focus();
		return false;
	}
	if(isNaN(value)){
		alert(name+ "必须是数字");
		$("#"+id)[0].focus();
		return false;
	}
	
	return true;
}
function checkInt(id, name){
	var value = $("#"+id).val();
	if(value.length==0){
		alert(name+ "不能为空");
		$("#"+id)[0].focus();
		return false;
	}
	if(parseInt(value)!=value){
		alert(name+ "必须是整数");
		$("#"+id)[0].focus();
		return false;
	}
	
	return true;
}

2.6 对于删除超链接,都需要进行确认操作

$(function(){
	$("a").click(function(){
		var deleteLink = $(this).attr("deleteLink");
		console.log(deleteLink);
		if("true"==deleteLink){
			var confirmDelete = confirm("确认要删除");
			if(confirmDelete)
				return true;
			return false;
			
		}
	});
})

3. adminNavigator.jsp

adminNavigator.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" isELIgnored="false"%>

<div class="navitagorDiv">
	<nav class="navbar navbar-default navbar-fixed-top navbar-inverse">
		<img style="margin-left:10px;margin-right:0px" class="pull-left" src="img/site/tmallbuy.png" height="45px">
		<a class="navbar-brand" href="#nowhere">天猫后台</a>
		
		<a class="navbar-brand" href="admin_category_list">分类管理</a>
		<a class="navbar-brand" href="admin_user_list">用户管理</a>
		<a class="navbar-brand" href="admin_order_list">订单管理</a>
	</nav>
</div>

 4. adminPage.jsp

这是分页JSP。分页功能不仅仅有前端效果,还需要结合服务端传递过来的数据综合才能起作用。

adminPage.jsp

5. adminFooter.jsp

页脚部分。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8" isELIgnored="false"%>

<div class="footer">
</div>

</body>
</html>

查询

1. MVC设计思想

根据MVC设计模式的思想,做J2EE web应用,从MVC的角度来看,就是把不同的数据显示在不同的页面上。

数据就是模型(bean,dao)

页面就是视图(jsp)

控制不同的模型显示在不同的视图上,这件事就是由控制器来完成(servlet)

所以分类管理,从MVC的角度来看,就是把多条分类Category数据放在一个集合里,让listCategory.jsp这个视图去显示出来。

MVC 设计思想

2. list()方法

根据filter+servlet的设计模式,访问地址:

http://127.0.0.1:8080/tmall/admin_category_list

会导致CategoryServlet的list犯法被调用。根据MVC设计思想,CategoryServlet充当的是C-控制器的角色。那么在list()方法里做的事情是,取出数据,并且交由jsp显示。

第二行: 通过categoryDAO取得数据集合 cs
第六行: 通过request.setAttribute 放在 “thecs" 这个key中,为后续服务端跳转到jsp之后使用。
第九行:return "admin/listCategory.jsp"; 服务端跳转到视图listCategory.jsp页面。
注: CategoryServlet的list()方法中可以直接使用categoryDAO,是因为其父类BaseBackServlet在属性中声明了

protected CategoryDAO categoryDAO = new CategoryDAO();

作为BaseBackServlet的子类,CategoryServlet可以直接使用该protected修饰的属性。除此之外,list里还有page对象。

	public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
		List<Category> cs = categoryDAO.list(page.getStart(),page.getCount());
		int total = categoryDAO.getTotal();
		page.setTotal(total);
		
		request.setAttribute("thecs", cs);
		request.setAttribute("page", page);
		
		return "admin/listCategory.jsp";
	}

3. 服务端跳转

list()方法返回字符串"admin/listCategory.jsp"就导致服务端跳转到了页面"admin/listCategory.jsp"。

为什么返回这个字符串,就可以实现服务端跳转呢?在Servlet中进行服务端跳转不是要如下格式吗?

request.getRequestDispatcher("xxx.jsp").forward(request,response);

这就要结合CategoryServlet的父类BaseBackServlet来理解了。在BaseBackServlet的70行,在接货组反射机制调用了list()方法之后,获取返回值redirect。

  • 如果redirect是以@开头的字符串,那么就进行客户端跳转
  • 如果redirect是以%开头的字符串,那么就直接输出字符串
  • 如果都不是,则进行服务端跳转
String redirect = m.invoke(this,request, response,page).toString();
             
            /*根据方法的返回值,进行相应的客户端跳转,服务端跳转,或者仅仅是输出字符串*/
             
            if(redirect.startsWith("@"))
                response.sendRedirect(redirect.substring(1));
            else if(redirect.startsWith("%"))
                response.getWriter().print(redirect.substring(1));
            else
                request.getRequestDispatcher(redirect).forward(request, response);

4. listCategory.jsp

最后就是视图listCategory.jsp了。作为视图,担当的主要角色就是显示数据。所以关键就是从44行开始,借助JSTL的c:forEach标签遍历从CategoryServlet的list()的request.setAttrbute("thecs",cs);传递过来的集合。

<c:forEach items="${thecs}" var="c">

分页

1. Page.java

Page这个类专门为分页提供必要信息。

属性:

  • int start;开始位置
  • int count;每页显示的数量
  • int total;总共有多少条数据
  • String param;参数

方法:

  • getTotalPage 根据每页显示的数量count以及总共有多少条数据total,计算出总共有多少页
  • getLast 计算出最后一页的数值是多少
  • isHasPrevious 判断是否有前一页
  • isHasNext 判断是否有后一页
package tmall.util;

public class Page {
	int start;
	int count;
	int total;
	String param;
	public int getStart() {
		return start;
	}
	public void setStart(int start) {
		this.start = start;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public Page(int start, int count) {
		super();
		this.start = start;
		this.count = count;
	}
	
	public boolean isHasPreviouse(){
		if(start==0)
			return false;
		return true;
		
	}
	public boolean isHasNext(){
		if(start==getLast())
			return false;
		return true;
	}
	
	public int getTotalPage(){
		int totalPage;
        // 假设总数是50,是能够被5整除的,那么就有10页
        if (0 == total % count)
            totalPage = total /count;
        // 假设总数是51,不能够被5整除的,那么就有11页
        else
            totalPage = total / count + 1;
        
        if(0==totalPage)
        	totalPage = 1;
        return totalPage;
		
	}
	
	public int getLast(){
		int last;
        // 假设总数是50,是能够被5整除的,那么最后一页的开始就是45
        if (0 == total % count)
            last = total - count;
        // 假设总数是51,不能够被5整除的,那么最后一页的开始就是50
        else
            last = total - total % count;
        
        last = last<0?0:last;
        return last;
	}
	
	public int getTotal() {
		return total;
	}
	public void setTotal(int total) {
		this.total = total;
	}
	public String getParam() {
		return param;
	}
	public void setParam(String param) {
		this.param = param;
	}
	
}

2. 获取分页参数

获取分页信息是在BaseBackServlet的service方法中

int start= 0;
int count = 5;
try {
	start = Integer.parseInt(request.getParameter("page.start"));
} catch (Exception e) {
	
}
try {
	count = Integer.parseInt(request.getParameter("page.count"));
} catch (Exception e) {
}
Page page = new Page(start,count);

获取网页上传来的开始位置,以及每页需要显示的数量。如果传递数据过来,开始位置取0,每页显示的数量取默认值:5。

3. 基于分页参数,获取数据

接着就是通过反射,访问了CategServlet的list方法,并在其中通过categoryDAO,基于page的start,和count值,获取分页数据。

List<Category> cs = categoryDAO.list(page.getStart(),page.getCount());

4. 为page对象设置总数

除此之外,还为page对象添加了总数。

int total = categoryDAO.getTotal();
page.setTotal(total);

为什么要加这个总数?因为在分页显示的时候,要根据这个总数来判断一共有多少页面,最后一页是多少。

接着通过:

request.setAttrbute("page",page);

把page对象,传参到了listCategory.jsp页面。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐