将web控制逻辑封装到 DispatchServlet 中,将业务功能提取到子控制中。
前端控制器:
/**
* 核心前端控制器,处理任何的 *.do 请求
* 前端控制器处理全部的Web功能
**/
public class DispatchServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
//创建子控制器
//执行子控制器方法
//根据子控制器的返回值,转发到JSP页面
request.setCharacterEncoding("UTF-8");
try {
//创建子控制器
Controller controller=new Controller();
//调用子控制器的方法(封装业务逻辑)
String path = controller.execute(request);
// path= "list"
path = "/WEB-INF/jsp/"+path+".jsp";
request.getRequestDispatcher(path)
.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
response.setContentType(
"text/html; charset=UTF-8");
PrintWriter out=response.getWriter();
out.println("系统故障:"+e.getMessage());
}
}
}
将作为视图组件的JSP文件放在 /WEB-INF/JSP中, 避免用户直接访问。
配置前端控制器:将所有 *.do 请求转到前端控制器进行处理。
<servlet>
<description></description>
<display-name>DispatchServlet</display-name>
<servlet-name>DispatchServlet</servlet-name>
<servlet-class>mvc.DispatchServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatchServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
public class Controller {
/**
* 子控制器方法
* @param request 用于在控制器和JSP直接传递数据
* @return 转发的目标JSP页面
* @throws Exception
*/
@RequestMapping("/list.do")
public String execute(
HttpServletRequest request)
throws Exception {
UserDAO dao = new UserDAO();
List<User> users = dao.findAll();
//将users数据传递到JSP
request.setAttribute("users", users);
return "list";
}
@RequestMapping("/add.do")
public String add(HttpServletRequest request)
throws Exception {
return "add";
}
}
视图组件的JSP文件放在 /WEB-INF/JSP中:
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<table>
<tr>
<td>ID</td>
<td>用户名</td>
<td>密码</td>
<td>邮箱</td>
<td>操作</td>
</tr>
<c:forEach items="${users}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.uname}</td>
<td>${user.pwd}</td>
<td>${user.email}</td>
<td>删除</td>
</tr>
</c:forEach>
</table>
</body>
</html>
原理:
@Retention(RetentionPolicy.RUNTIME) //注解一值保留到运行期
@Target(ElementType.METHOD) //此注解只能写在方法上
public @interface RequestMapping {
String value();
}
/**
* 处理者,处理器
*/
public class Handler {
/**
* 子控制器对象
*/
private Object controller;
/**
* 子控制的方法
*/
private Method method;
public Handler() {
}
public Handler(Object controller, Method method) {
this.controller = controller;
this.method = method;
}
@Override
public String toString() {
return "Handler [controller=" + controller + ", method=" + method + "]";
}
public String execute(HttpServletRequest req)
throws Exception {
//利用反射执行方法
return (String)method.invoke(controller, req);
}
}
HandlerMapping的内部封装了一个Map,利用Map优化查询效率,提高性能。
/**
* 用于管理,URL与子控制器方法的映射关系
* 如:
* /list.do -> Handler(controller, execute())
* /add.do -> Handler(controller, add())
*/
public class HandlerMapping {
private Map<String, Handler> mapping=
new HashMap<String, Handler>();
public HandlerMapping() {
}
/**
* 将 一个控制器类解析并且添加到map中
* 1. 动态加载类到内存
* 2. 找到全部的方法,并且查找方法上是否有
* RequestMapping 注解
* 3. 如果有注解,就将注解URL,和控制器以及方法添加到
* map中
* @param className 一个控制器类名
*/
public void parseController(String className)
throws Exception {
//加载类到内存中
Class cls = Class.forName(className);
//创建控制器对象
Object controller = cls.newInstance();
//找到全部方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
//在方法上查找 RequestMapping 注解
RequestMapping ann=
method.getAnnotation(RequestMapping.class);
//如果ann不为空,则方法上包含RequestMapping注解
if(ann!=null) {
String url=ann.value();
//将找到的方法添加到map中
mapping.put(url,
new Handler(controller, method));
}
}
}
@Override
public String toString() {
return "HandlerMapping [mapping=" + mapping + "]";
}
/**
* 根据请求的路径url,找到Handler对象
* @param url
* @return
*/
public Handler get(String url) {
return mapping.get(url);
}
public static void main(String[] args)
throws Exception {
HandlerMapping handlerMapping=
new HandlerMapping();
handlerMapping.parseController("test.Demo");
System.out.println(handlerMapping.mapping);
}
}
注意:务必利用main方法对HandlerMapping进行单元测试!
测试素材:
public class Demo {
@RequestMapping("/list.do")
public String list() {
return "list";
}
@RequestMapping("/add.do")
private String add() {
return "add";
}
}
mvc.xml: 配置控制器其类
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="mvc.Controller"/>
</beans>
DispatchServlet:
private HandlerMapping handlerMapping;
@Override
public void init() throws ServletException {
try {
handlerMapping = new HandlerMapping();
//handlerMapping.parseController(
// "mvc.Controller");
SAXReader reader = new SAXReader();
//读取web.xml中的Servlet配置参数
String file=getInitParameter("config");
System.out.println("file:"+file);
InputStream in = getClass()
.getClassLoader()
.getResourceAsStream(file);
Document doc = reader.read(in);
Element root=doc.getRootElement();
//找到全部 bean 元素
List<Element> list=root.elements("bean");
for (Element element : list) {
//获取bean元素上的class属性
String className=
element.attributeValue("class");
System.out.println("Controller:"+className);
//将得到的类名交给 handlerMapping
handlerMapping.parseController(className);
}
in.close();
System.out.println(handlerMapping);
}catch (Exception e) {
e.printStackTrace();
throw new ServletException(
"控制器解析错误", e);
}
}
在web.xml 中添加 load-on-startup 在容器启动时候初始化 Servlet 测试init方法
<servlet>
<description></description>
<display-name>DispatchServlet</display-name>
<servlet-name>DispatchServlet</servlet-name>
<servlet-class>mvc.DispatchServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatchServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
重构service()方法,支持重定向功能:
protected void service( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //创建子控制器 //执行子控制器方法 //根据子控制器的返回值,转发到JSP页面 request.setCharacterEncoding("UTF-8"); try {
//获取用户请求的路径
String path = request.getRequestURI();
System.out.println(path);
String contextPath=request.getContextPath();
System.out.println(contextPath);
path = path.substring(contextPath.length());
System.out.println(path);
//根据请求的URL找到Handler
Handler handler=handlerMapping.get(path);
//执行控制器方法(利用反射执行方法)
path = handler.execute(request);
//创建子控制器
//Controller controller=new Controller();
//调用子控制器的方法(封装业务逻辑)
//String path = controller.execute(request);
if(path.startsWith("redirect:")) {
//支持重定向功能
path = path.substring(
"redirect:".length());
if(path.startsWith("http")) {
//如果是http开头的就直接重定向
response.sendRedirect(path);
} else {
// 否则就拼接绝对路径
// /servler6-4/list.do
path = contextPath+path;
response.sendRedirect(path);
}
} else {
//转发到JSP
path = "/WEB-INF/jsp/"+path+".jsp";
request.getRequestDispatcher(path)
.forward(request, response);
}
} catch (Exception e) {
e.printStackTrace();
response.setContentType(
"text/html; charset=UTF-8");
PrintWriter out=response.getWriter();
out.println("系统故障:"+e.getMessage());
}
}
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
<form action="save.do" method="post">
<div>
<label>用户名:</label>
<input type="text" name="uname"
value="${uname}">
<span>${unameError}</span>
</div>
<div>
<label>密码:</label>
<input type="password" name="pwd">
</div>
<div>
<label>e-mail:</label>
<input type="text" name="email"
value="${email}">
</div>
<div>
<input type="submit" value="保存">
</div>
</form>
</body>
</html>
public class Controller {
/**
* 子控制器方法
* @param request 用于在控制器和JSP直接传递数据
* @return 转发的目标JSP页面
* @throws Exception
*/
@RequestMapping("/list.do")
public String execute(
HttpServletRequest request)
throws Exception {
UserDAO dao = new UserDAO();
List<User> users = dao.findAll();
//将users数据传递到JSP
request.setAttribute("users", users);
return "list";
}
@RequestMapping("/add.do")
public String add(HttpServletRequest request)
throws Exception {
return "add"; //add.jsp
}
@RequestMapping("/save.do")
public String save(HttpServletRequest request)
throws Exception {
String uname=request.getParameter("uname");
String pwd=request.getParameter("pwd");
String email=request.getParameter("email");
UserDAO dao = new UserDAO();
//检查用户名是否是同名的
User u = dao.find(uname);
if(u!=null) {
//返回到添加页面,继续输入信息
request.setAttribute("uname", uname);
request.setAttribute("email", email);
request.setAttribute("unameError",
"用户名重复");
return "add";
}
User user = new User();
user.setUname(uname);
user.setPwd(pwd);
user.setEmail(email);
dao.save(user);
request.setAttribute("message", "成功");
//return "success"; //success.jsp
return "redirect:/list.do";
//return "redirect:http://tmooc.cn";
}
@RequestMapping("/delete.do")
public String delete(HttpServletRequest request)
throws Exception{
String str = request.getParameter("id");
int id = Integer.parseInt(str);
UserDAO dao = new UserDAO();
dao.delete(id);
return "redirect:/list.do";
}
}
测试 列表,添加,删除功能
<%@ page language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<form action="login.do" method="post">
<div>
<label>用户名:</label>
<input type="text" name="uname"
value="${uname}">
<span>${unameError}</span>
</div>
<div>
<label>密码:</label>
<input type="password" name="pwd">
<span>${pwdError}</span>
</div>
<div>
<input type="submit" value="登录">
</div>
</form>
</body>
</html>
public class LoginController {
@RequestMapping("/login-form.do")
public String form(HttpServletRequest request)
throws Exception{
return "login";
}
@RequestMapping("/login.do")
public String login(HttpServletRequest request)
throws Exception{
//检查用户名和密码是否正确
String uname = request.getParameter("uname");
String pwd = request.getParameter("pwd");
UserDAO dao=new UserDAO();
User user = dao.find(uname);
//数据中没有用户信息,拒绝登录返回 "login"
if(user==null) {
request.setAttribute("unameError",
"用户名或密码错误");
request.setAttribute("uname", uname);
return "login";
}
if(user.getPwd().equals(pwd)) {
//密码也一样则登录成功!
request.getSession().setAttribute(
"loginUser", user);
return "redirect:/list.do";
}//密码不同,返回登录页面继续登录
request.setAttribute("pwdError",
"用户名或密码错误");
request.setAttribute("uname", uname);
return "login";
}
}
登录成功以后需要将登录信息保存到session中
过滤器:
public class AccessFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
//如果当前请求是 /login 开始的,就放过
String path = req.getRequestURI();
String ctxPath = req.getContextPath();
path = path.substring(ctxPath.length());
if(path.startsWith("/login")) {
chain.doFilter(req, res);
return;
}
HttpSession session = req.getSession();
//检查登录用户信息
User user=(User)session.getAttribute("loginUser");
if(user==null) {
//没有登录过,重定向到登录页面
res.sendRedirect(req.getContextPath()+
"/login-form.do");
}else {
//如果登录了就放过
chain.doFilter(req, res);
}
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
配置过滤器 web.xml
<filter>
<display-name>AccessFilter</display-name>
<filter-name>AccessFilter</filter-name>
<filter-class>mvc.AccessFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AccessFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
测试。