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) 及之前的攔截器。