1 前言
springMVC 可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义拦截器可以实现 HandlerInterceptor 接口,也可以继承 HandlerInterceptorAdapter 适配器,需要实现或重写以下3个方法:
- preHandle:此方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果用户决定该拦截器对请求进行拦截处理后还要调用其他拦截器或业务处理器进行处理,则返回 true;如果用户决定不需要再调用其他的组件去处理请求,则返回 false。
- postHandle:此方法在业务处理器处理完请求之后,在 DispatcherServlet 向客户端返回响应前被调用,此时已生成 ModelAndView 在该方法中对用户请求 request 进行处理。
- afterCompletion:此方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中可以进行一些资源清理的操作。
2 实验环境
(1)导入 JAR 包
(2)工作目录
(3)配置文件
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!-- 首页网页 -->
<welcome-file-list>
<welcome-file>/WEB-INF/view/index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置核心(前端)控制器 DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 加载IOC容器配置文件 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
applicationContext.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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
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-4.0.xsd">
<!-- 扫描组件,将加@Controller注解的类作为SpringMVC的控制层 -->
<context:component-scan base-package="com.test"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="com.test.MyInterceptor"></bean> <!-- 默认拦截所有请求 -->
</mvc:interceptors>
</beans>
也可以通过以下方式配置拦截器:首先在 MyInterceptor 类上加 @Component 注解,再在 applicationContext.xml 文件中配置如下:
<mvc:interceptors>
<ref bean="myInterceptor"/>
</mvc:interceptors>
(4)视图
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>首页</title>
</head>
<body>
<a href="test">测试Interceptor</a>
</body>
</html>
success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>成功</title>
</head>
<body>
SUCCESS
</body>
</html>
3 案例分析
3.1 测试拦截器执行顺序
3.1.1 测试一
MyInterceptor.java
package com.test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("1.preHandle");
return true; //true为放行,false为拦截
}
@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("3.afterCompletion");
}
}
Test.java
package com.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Test {
@RequestMapping(value="test")
public String test(){
System.out.println("test");
return "success";
}
}
在地址栏输入:http://localhost:8080/SpringMVC/,显示如下:
点击【测试Interceptor】,进入 success.jsp 页面,控制台输出如下:
1.preHandle
test
2.postHandle
3.afterCompletion
3.1.2 测试二
将 MyInterceptor 类中的 preHandle 返回值设置为 false, 点击【测试Interceptor】后没有进入 success.jsp 页面,页面显示空白,如下:
控制台输出如下:
1.preHandle
由此说明:preHandle 返回值 true 为放行,false 为拦截
3.1.3 测试三
MyInterceptor 类同测试一,将 Test 类中的 test 方法输出值改为:
System.out.println(1/0);
点击【测试Interceptor】后,控制台输出如下:
1.preHandle
3.afterCompletion
六月 08, 2020 5:04:48 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet [dispatcherServlet] in context with path [/SpringMVC] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
由此说明:afterCompletion 方法被放在一个 finally 块中,不管是否抛出异常,都得执行。
3.2 自定义拦截方式
applicationContext.xml 中配置的拦截器对所有请求都有效,有时只需拦截特定的请求,这时可以在 applicationContext.xml 中自定义拦截方式,如下:
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/test"/> <!-- 此拦截器作用于test -->
<mvc:exclude-mapping path="/demo"/> <!-- 此拦截器不作用于demo -->
<bean class="com.test.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
注意:有时 path="/**",表示所有文件夹及里面的子文件夹;有时 path="/*",表示所有文件夹,但不含子文件夹。
3.3 多个拦截器执行顺序
3.3.1 配置多个拦截器
applicationContext.xml 中配置多个拦截器的方法如下:
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="com.test.MyInterceptor1"></bean>
<bean class="com.test.MyInterceptor2"></bean>
<bean class="com.test.MyInterceptor3"></bean>
</mvc:interceptors>
注意:拦截器配置的顺序将决定拦截器执行顺序。配置的拦截器简记为“拦截器数组”。
3.3.2 拦截器执行顺序源码分析
springMVC 中,由 HandlerExecutionChain 类管理多个拦截器的执行顺序,在 Dispatcher 类中的 doDispatch 方法中调用拦截器的 preHandle、postHandle、afterCompletion 方法。
(1)Dispatcher 类中的 doDispatch 方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
HandlerExecutionChain mappedHandler = null;
...
try {
...
try {
...
mappedHandler = getHandler(processedRequest);
...
if (!mappedHandler.applyPreHandle(processedRequest, response)) { //调用preHandle方法
return;
}
try { //执行请求方法,并获取ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
...
mappedHandler.applyPostHandle(processedRequest, response, mv); //调用postHandle方法
}
catch (Exception ex) { //能捕获所有异常,捕获异常后,接着执行后面的语句
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
...
if (mv != null && !mv.wasCleared()) {
render(mv, request, response); //渲染视图
...
}
...
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null); //调用afterCompletion方法
}
}
由以上源码知:当碰到 preHandle 方法返回值为 false 的拦截器时,所有拦截器的 postHandle 方法都不会执行。
(2)preHandle 的执行顺序源码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (getInterceptors() != null) {
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
if (!interceptor.preHandle(request, response, this.handler)) { //调用preHandle方法
triggerAfterCompletion(request, response, null); //调用afterCompletion方法
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
由以上源码知:preHandle 按照拦截器数组的正向顺序执行,当碰到 preHandle 方法返回值为 false 的拦截器时,后面的拦截器的 preHandle 方法都不执行,开始执行 triggerAfterCompletion 方法,并将 interceptorIndex 设置为返回 false 的前一个拦截器对应的数组索引。
(3)postHandle 的执行顺序源码
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
由以上源码知:postHandle 按照拦截器数组的反向顺序执行。
(4)afterCompletion 的执行顺序源码
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
if (getInterceptors() == null) {
return;
}
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
由以上源码知:afterCompletion 按照拦截器数组的反向顺序执行,并且只执行数组索引为 interceptorIndex(第一个返回 false 的拦截器对应的数组索引-1) 及之前的拦截器。