2.2、拦截器
2.2.1、简介
在 DispatcherServlet 中 映射器 HandlerMapping 会找到一个Handler和与之对应的拦截器组成一个执行链返回给DispatcherServlet,并且在 Handler 执行前后,调用拦截器的一些方法进行处理。
![image-20230303140651479](https://yudejava.oss-cn-hangzhou.aliyuncs.com/typora/202303031406541.png)
这个拦截器就是Spring MVC 提供的一种强大的功能组件,可以在请求进入控制前、控制器处理完请求后、渲染完视图后、可以执行的一些额外操作。类似于Servlet中的过滤器,可以做一些权限认证、记录请求日志、判断用户状态等等一些操作。(比方说很常见的一个功能:判断用户是否登录,没有登录不让操作,会自动跳转到登录界面。这个在很多网站都会有,此功能就能通过拦截器实现,登录时将用户登录信息保存到session中,在进行其他操作时,在拦截器中判断session中是否有用户信息,如果没有就跳转到登录界面,如果有就放行)。
Spring MVC 中有一个包: org.springframework.web.servlet ,包中提供了一个 HandlerInterceptor 接口,该接口包含 3 个方法,这个接口就是拦截器的顶层接口。
public interface HandlerInterceptor {
//该方法在控制器方法之前执行,其返回值用来表示是否中断后续操作。
//返回值为 true 时,表示继续向下执行;
//返回值为 false 时,表示中断后续的操作
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
//在控制器方法调用之后,解析式图之前执行。我们可以通过此方法对请求域中的模型(Model)数据和视图做出进一步的修改。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
//会在视图渲染结束之后执行。我们可以通过该方法实现资源清理、日志记录等工作。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
通过这三个方法我们就能确定拦截器的执行流程了:
2.2.2、开发使用
拦截器必须实现 HandlerInterceptor接口,它内部的三个方法都是default修饰的,所以可以随意重写其中的方法。
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法执行:"+request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法执行"+request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion方法执行"+request.getRequestURI());
}
}
实现HandlerInterceptor后如果需要让它拦截Handler,还需要在SprinMVC的XML配置文件中进行配置,才会使其生效。标签为:mvc:interceptors,它内部有三种字标签,用于拦截器的配置
一级子标签 | 二级子标签 | 说明 |
---|---|---|
bean | 无 | 用于定义一个全局拦截器,对所有的请求进行拦截。 |
ref | 无 | 引入一个外部定义的Bean作为全局拦截器。需要配和bean 标签使用 |
mvc:interceptor | 按从上到下的顺序依次配置: mvc:mapping:用来配置拦截器拦截的路径 mvc:exclude-mapping:来配置不需要被拦截器拦截的路径。 bean :只拦截指定的路径,非全局 | 定义一个指定拦截路径的拦截器。 |
根据子标签的不同含义,可以配置不同范围的拦截器,需要合理的使用:
-
配置MyHandlerInterceptor为全局拦截器(使用Bean)
<!--配置拦截器--> <mvc:interceptors> <!--这个拦截器是全局拦截器拦截全部请求--> <bean class="com.yu.interceptor.MyHandlerInterceptor"/> <!--如果还有其他的拦截器,可以继续配置 --> </mvc:interceptors>
-
配置MyHandlerInterceptor为全局拦截器(使用ref)
<bean id="interceptor" class="com.yu.interceptor.MyHandlerInterceptor"/> <!--配置拦截器--> <mvc:interceptors> <!--这个引入的拦截器也是全局的,拦截全部请求--> <ref bean="interceptor"/> <!--如果还有其他的拦截器,可以继续配置 --> </mvc:interceptors>
-
配置非全局拦截器
<!--配置拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 配置拦截器拦截的请求路径,'/**' 是拦截所有的--> <mvc:mapping path="/**"/> <!--配置拦截器不需要拦截的请求路径--> <mvc:exclude-mapping path="/index"/> <mvc:exclude-mapping path="/disposeParams/testJson"/> <!--定义在mvc:interceptor内的拦截器,非全局,只能拦截mvc:interceptor中规定的路径--> <bean class="com.yu.interceptor.MyHandlerInterceptor"></bean> </mvc:interceptor> <!--如果还有其他的拦截器,可以继续配置 --> </mvc:interceptors>
2.2.3、多拦截器机制
拦截器通常不会只设置一个,如果配置了多个拦截器,这些拦截器是有先后顺序的,我们先定义两个拦截器,看看他们是怎么执行的
public class MyHandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("第一个拦截器preHandle方法执行:"+request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("第一个拦截器postHandle方法执行"+request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("第一个拦截器afterCompletion方法执行"+request.getRequestURI());
}
}
public class MyHandlerInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("第二个拦截器preHandle方法执行:"+request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("第二个拦截器postHandle方法执行"+request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("第二个拦截器afterCompletion方法执行"+request.getRequestURI());
}
}
将两个拦截器在XML文件中进行配置:
<!--配置拦截器-->
<mvc:interceptors>
<!--拦截路径-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/disposeParams/testJson"/>
<bean class="com.yu.interceptor.MyHandlerInterceptor1"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/disposeParams/testJson"/>
<bean class="com.yu.interceptor.MyHandlerInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
启动后,执行一个方法看拦截器的执行顺序:
从上面可以总结出来多个拦截器的执行顺序:
![image-20220916132520025](https://yudejava.oss-cn-hangzhou.aliyuncs.com/typora/202303012119511.png)
如果preHandle前置方法返回的false,那么要根据它的所在拦截器的位置进行分析:
-
如果返回 false 的拦截器是第一个拦截器,执行完第一个拦截器的preHandle方法后就直接结束
//将第一个拦截器的preHandle方法的返回值改成false,第二个拦截器还是为true @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("第一个拦截器preHandle方法执行:"+request.getRequestURI()); return false; }
只执行了第一个preHandle方法后,就直接结束了,后面的没有执行
-
如果返回 false 的拦截器是不第一个拦截器
- 它和它前面的拦截器的 preHandle方法都执行,后面拦截器的不执行
- 所有拦截器的 postHandle 方法都不执行
- 它前面的所有拦截器的 afterComplation 方法都执行,后面拦截器的不执行
//将第一个拦截器的preHandle方法的返回值改成true,第二个拦截器的改为false @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("第二个拦截器preHandle方法执行:"+request.getRequestURI()); return false; }
第一个拦截和第二个拦截器的preHandle方法都执行了,但是所有的postHandle 都没有执行,而第一个拦截器的 afterComplation 方法执行了。