springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 控制器包扫描 --> <context:component-scan base-package="cw.springmvc.controller"/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 异常处理组件扫描 --> <context:component-scan base-package="cw.springmvc.handler"/> <!-- 注解驱动器 --> <mvc:annotation-driven/> </beans>
测试
拦截器
拦截器概述
- 拦截器需要实现框架中的指定接口,HandlerInterceptor,实现这个接口的类都为拦截器
- 拦截器的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
- 拦截器与过滤器类似,过滤器是用来过滤器请求参数,设置编码字符集等工作;拦截器是拦截用户的请求,做请求做判断处理的。
- 拦截器的拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。
- 拦截器是全局的,可以对多个Controller做拦截。 一个项目中可以有0个或多个拦截器, 他们在一起拦截用户的请求。
- 拦截器常用在:用户登录处理,权限检查, 记录日志。
- 拦截器的使用步骤:
- 1.定义类实现HandlerInterceptor接口
- 2.在springmvc配置文件中,声明拦截器, 让框架知道拦截器的存在。
- 拦截器的执行时间:
- 1)在请求处理之前, 也就是controller类中的方法执行之前先被拦截。
- 2)在控制器方法执行之后也会执行拦截器。
- 3)在请求处理完成后也会执行拦截器。
创建拦截器类
- 自定义拦截器,需要实现HandlerInterceptor接口,然后根据需求实现HandlerInterceptor接口中的三个方法
拦截器类方法讲解
package cw.springmvc.handler; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * ClassName: MyInterceptor * Package: cw.springmvc.handler * Description: * 拦截器类,拦截用户的请求 * * @Author tcw * @Create 2023-06-05 23:40 * @Version 1.0 */ public class MyInterceptor implements HandlerInterceptor{ /** * preHandle 预处理方法 * 该方法在控制器方法执行前执行,用户的请求会先到达此方法 * 在该方法中可以获取请求的信息,验证请求信息是否符合要求 * 可以验证用户是否登录, 验证用户是否有权限访问某个连接地址(url)。 * 如果验证失败,可以截断请求,请求不能被处理;如果验证成功,可以放行请求,此时控制器方法才能执行。 * * 重要:是整个项目的入口,门户。 * 当preHandle返回true 请求可以被处理。 * preHandle返回false,请求到此方法就截止。 * * @param request * @param response * @param handler 被拦截的控制器对象 * @return 布尔值 * true: 请求是通过了拦截器的验证,可以执行处理器方法。 * false: 请求没有通过拦截器的验证,请求到达拦截器就截止了。 请求没有被处理 * 后面的方法:处理器方法、后处理方法、afterCompletion都不执行 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return HandlerInterceptor.super.preHandle(request, response, handler); } /** * postHandle:后处理方法。 * 在处理器方法之后执行的(MyController.doSome()) * 能够获取到处理器方法的返回值ModelAndView, * 可以修改ModelAndView中的数据和视图,可以影响到最后的执行结果。 * 主要是对原来的执行结果做二次修正 * * @param request * @param response * @param handler 被拦截的处理器对象 Controller * @param modelAndView 处理器方法的返回值 * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } /** * afterCompletion:最后执行的方法 * 在请求处理完成后执行的。 * 框架中规定是当你的视图处理完成后,对视图执行了forward。就认为请求处理完成。 * 一般做资源回收工作的, 程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收。 * * @param request * @param response * @param handler 被拦截器的处理器对象 * @param ex 程序中发生的异常 * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
拦截器图示
SpringMVC 配置文件中配置拦截器
<!-- 配置拦截器,拦截器可以配置0到多个 --> <mvc:interceptors> <!-- 配置第一个拦截器 --> <mvc:interceptor> <!-- 指定拦截器拦截的URI地址 可以使用通配符 ** 表示任意的字符,文件或者多级目录和目录中的文件 /user/** 以user开头的请求地址都会被该拦截器拦截 /** 该拦截器会拦截所有的请求 --> <mvc:mapping path="/**"/> <!-- 需要进行拦截的地址,对应的拦截器对象 --> <bean class="cw.springmvc.handler.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
拦截器中方法编写
package cw.springmvc.handler; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * ClassName: MyInterceptor * Package: cw.springmvc.handler * Description: * 拦截器类,拦截用户的请求 * * @Author tcw * @Create 2023-06-05 23:40 * @Version 1.0 */ public class MyInterceptor implements HandlerInterceptor{ private long btime = 0; // 用于统计请求的处理时间 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器preHandle预处理方法执行..."); btime = System.currentTimeMillis(); // 记录请求开始处理时间 // 返回真,后面的方法才能执行 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器postHandle后处理方法执行..."); // 原来的doSome执行结果需要调整,进行modelAndView修改。 if( modelAndView != null){ //修改数据 // modelAndView.addObject("mydate",new Date()); //修改视图 modelAndView.setViewName("other"); } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器afterCompletion:最后执行的方法执行..."); long etime = System.currentTimeMillis(); // 请求处理结束时间 System.out.println("计算从preHandle到请求处理完成的时间:"+(etime - btime )); } }
控制器
@Controller public class MyController { @RequestMapping(value = "/some.do") public ModelAndView doSome(String name, Integer age) throws MyUserException { System.out.println("控制器方法执行..."); ModelAndView mv = new ModelAndView(); mv.addObject("name", name); mv.addObject("age", age); mv.setViewName("show"); return mv; } }
测试
拦截器执行流程图示
多个拦截器的执行顺序
- 拦截器在配置文件中,先声明的会先执行,后声明的后执行,声明的若干个拦截器在框架中是使用ArrayList集合进行保存的,是按照声明的先后顺序放入到ArrayList集合中
多个拦截器代码实现
- 拦截器1
package cw.springmvc.handler; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * ClassName: MyInterceptor * Package: cw.springmvc.handler * Description: * 拦截器类,拦截用户的请求 * * @Author tcw * @Create 2023-06-05 23:40 * @Version 1.0 */ public class MyInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器-1-preHandle预处理方法执行..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器-1-postHandle后处理方法执行..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器-1-afterCompletion:最后执行的方法执行..."); } }
- 拦截器2
package cw.springmvc.handler; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * ClassName: MyInterceptor * Package: cw.springmvc.handler * Description: * 拦截器类,拦截用户的请求 * * @Author tcw * @Create 2023-06-05 23:40 * @Version 1.0 */ public class MyInterceptor2 implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器-2-preHandle预处理方法执行..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器-2-postHandle后处理方法执行..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器-2-afterCompletion:最后执行的方法执行..."); } }
- 拦截器配置
<!-- 配置拦截器,拦截器可以配置0到多个 --> <mvc:interceptors> <!-- 配置第一个拦截器 --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cw.springmvc.handler.MyInterceptor"/> </mvc:interceptor> <!-- 配置第二个拦截器 --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cw.springmvc.handler.MyInterceptor2"/> </mvc:interceptor> </mvc:interceptors>
多个拦截器执行顺序图示
不同拦截器 preHandle 不同返回值分析
- 只有当拦截器的方法preHandle返回true才可以向后执行,如果其中一个拦截器的preHandle方法返回值为false,那么请求会被拦截,不会被控制器方法处理,即只要有拦截器的preHandle方法返回值为false那么处理器方法就不会执行
- 拦截器-1-preHandle返回true,拦截器-2-preHandle返回false
- 拦截器-1-preHandle返回false,拦截器-2-preHandle返回true
- 只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,其后续的处理器方法与 postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中preHandle()方法返回 true相对应的afterCompletion()方法。最终都会给出响应。
拦截器和过滤器的区别
- 过滤器是servlet规范中的对象,拦截器是框架中自己定义的对象,拦截器局限于框架之中,离开了SpringMVC框架,拦截器就不能使用了
- 过滤器实现Filter接口的对象, 拦截器是实现HandlerInterceptor
- 过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的。拦截器是用来验证请求的,能截断请求。
- 过滤器是在拦截器之前先执行的。
- 过滤器是tomcat服务器创建的对象。拦截器是springmvc容器中创建的对象
- 过滤器是一个执行时间点。拦截器有三个执行时间点
- 过滤器可以处理jsp,js,html等等。拦截器是侧重拦截对Controller的请求, 如果你的请求不能被DispatcherServlet接收, 这个请求不会执行拦截器内容
- 拦截器拦截普通类方法(控制器方法)执行,过滤器过滤servlet请求响应
- 控制器其实是普通的Java类,框架给了控制器能够处理请求的能力
拦截器案例:登录验证
- 注意,前端控制器DispatcherServlet的路径配置,不同的路径配置将会影响到对jsp页面的请求是否会交给DispatcherServlet处理,
/
DispatcherServlet不会处理jsp页面的请求,/*
DispatcherServlet会处理jsp页面的请求,*.do
DispatcherServlet不会处理jsp页面的请求
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="./login" method="post"> username:<input type="text" name="username"><br/> <input type="submit" name="submit"><br/> </form> </body> </html>
controller
@Controller public class MyController { @RequestMapping(value = "/login") public ModelAndView doSome(String name, Integer age) throws MyUserException { System.out.println("控制器方法执行..."); ModelAndView mv = new ModelAndView(); mv.setViewName("login"); return mv; } }
拦截器
public class MyInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取登录的用户名 String username = request.getParameter("username"); System.out.println(username); // 判断会话作用域中是否有用户名,判断是否登录过 Object username1 = request.getSession().getAttribute("username"); System.out.println(username1); // 验证输入的用户名 if (username == null || username.length() == 0 || !"admin".equals(username)) { // 验证是否登录过 if (username1 == null || !"admin".equals(username1)) { // 验证失败 request.getRequestDispatcher("./tips.jsp").forward(request, response); return false; } } // 验证成功 // 将用户名添加到会话作用域 request.getSession().setAttribute("username", username); return true; } }
jsp
- login.jsp
<%-- Created by IntelliJ IDEA. User: cw Date: 2023-06-06 Time: 10:00 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>模拟登录</h1> <p>用户【<%= session.getAttribute("username") %>】登录成功</p> <a href="./logout.jsp">退出登录</a> </body> </html>
- logout.jsp
<%-- Created by IntelliJ IDEA. User: cw Date: 2023-06-06 Time: 10:03 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <p>【<%= session.getAttribute("username") %>】退出登录</p> </body> </html> <% // 删除信息 session.removeAttribute("username"); %>
- tips.jsp
<%-- Created by IntelliJ IDEA. User: cw Date: 2023-06-06 Time: 10:07 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>登录失败</h1> </body> </html>
DispatcherServlet 配置
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 控制器包扫描 --> <context:component-scan base-package="cw.springmvc.controller"/> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 配置拦截器,拦截器可以配置0到多个 --> <mvc:interceptors> <!-- 配置第一个拦截器 --> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cw.springmvc.handler.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
运行测试
- 再直接浏览器访问:http://localhost:8080/springmvc/login
SpringMVC 执行流程
- 浏览器发送请求,请求会最先被Tomcat接收,Tomcat将和DispatcherServlet对应路径相匹配的请求交给DispatcherServlet进行处理
- DispatcherServlet调用处理器映射器,处理器映射器根据请求,从SpringMVC容器中获取处理器对象
- 相当于执行:
SpringMVC容器对象.getBean("beanName")
- 处理器映射器:SpringMVC中的一种对象,框架把实现了HandlerMapping接口的类都叫映射器
- 处理器映射器对象有多个
- 处理器映射器接口HandlerMapping有很多子接口和子类,因为在没有注解之前需要通过实现接口来创建控制器,控制器种类一多,接口也就多了
HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);
- 框架会把找到的处理器对象放到处理器执行链的类中进行保存,将处理器执行链给DispatcherServlet
public class HandlerExecutionChain { private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); private final Object handler; // 保存处理器对象 private final List<HandlerInterceptor> interceptorList; // 项目中所有的拦截器 private int interceptorIndex; }
- DispatcherServlet将处理器执行链中的处理器对象交给处理器适配器,处理器适配器调用执行处理器的方法,将处理器方法的返回值ModelAndView返回给中央调度器
- 处理器适配器:SpringMVC框架中的对象,需要实现HandlerAdapter
- 处理器适配器对象有多个
- 处理器适配器接口HandlerAdapter有很多子接口和子类,因为适配器的种类多
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- DispatcherServlet接收到处理器适配器的返回结果ModelAndView,调用视图解析器,将ModelAndView交给视图解析器,根据使用解析器的配置,组成视图的完整路径,并创建View对象,将视图对象交给DispatcherServlet
- 视图解析器需要实现ViewResoler接口
- 视图解析器可以存在多个
- View是一个接口,表示视图,在框架中,jsp、html是使用View和其实现类表示
mv.setView(new RedirectView("/a.jsp"))
和mv.setViewName("a"))
效果差不多,只不过前者是显示创建和使用视图对象- InternalResourceView表示jsp文件的视图类,视图解析器会创建InternalResourceView对象,这对象中有相应的jsp文件的路径url
- DispatcherServlet调用视图View,调用视图View对象的方法将Model中的数据放到request作用域中,视图将数据放到视图页面中,DispatcherServlet最后将视图响应给浏览器,向客户端呈现处理结果
- 使用中央调度器按照处理流程调用相应的对象在不同阶段进行处理,有利于解耦合,其中某个对象进行了修改,不会影响其他对象的正常执行