個 登 錄 有 必 要 做 成 那 樣 嗎 ? \textcolor{green}{一個登錄有必要做成那樣嗎?}一個登錄有必要做成那樣嗎?我 的 回 答 : 非 常 有 必 要 \textcolor{red}{我的回答:非常有必要}我的回答:非常有必要
試 想 一 下 , 如 果 一 個 網 站 可 以 隨 便 進 去 點 贊 等 操 作 , 那 還 要 登 錄 干 什 么 ? \textcolor{green}{試想一下,如果一個網站可以隨便進去點贊等操作,那還要登錄干什么?}試想一下,如果一個網站可以隨便進去點贊等操作,那還要登錄干什么?
博 主 也 在 學 習 階 段 , 如 若 發 現 問 題 , 請 告 知 , 非 常 感 謝 \textcolor{Orange}{博主也在學習階段,如若發現問題,請告知,非常感謝}博主也在學習階段,如若發現問題,請告知,非常感謝
周 榜 也 到 了 26 , 哇 塞 ( o ゜ ▽ ゜ ) o ☆ , 順 便 提 一 下 今 天 庫 里 必 登 三 分 歷 史 第 一 \textcolor{blue}{周榜也到了26,哇塞(o゜▽゜)o☆,順便提一下今天庫里必登三分歷史第一}周榜也到了26,哇塞(o゜▽゜)o☆,順便提一下今天庫里必登三分歷史第一
SpringMVC的處理器攔截器類似于Servlet開發中的過濾器Filter,用于對處理器進行預處理和后處理。
攔截器和過濾器的區別在于攔截器使AOP思想的具體應用
? ? > 新 建 一 個 M o d u l e , 添 加 w e b 支 持 \textcolor{OrangeRed}{--> 新建一個Module,添加web支持}??>新建一個Module,添加web支持
? ? > 配 置 w e b . x m l , a p p l i c a t i o n C o n t e x t . x m l , 添 加 一 個 c o n t r o l l e r 的 包 \textcolor{OrangeRed}{--> 配置web.xml,applicationContext.xml,添加一個controller的包}??>配置web.xml,applicationContext.xml,添加一個controller的包
? ? > 編 寫 測 試 \textcolor{OrangeRed}{--> 編寫測試}??>編寫測試
@RestController
public class TestController {
@GetMapping("/t1")
public String test(){
System.out.println("TestController-->test()執行了");
return "ok";
}
}
12345678
添加Artifact中的lib,以及配置Tomcat,啟動測試出現,證明Spring配置好了
? ? > 編 寫 攔 截 器 \textcolor{OrangeRed}{--> 編寫攔截器}??>編寫攔截器
package com.hxl.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
//在請求處理的方法之前執行
//return true;執行下一個攔截器
//如果返回false就不執行下一個攔截器
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("------------處理前------------");
return true;
}
//在請求處理方法執行之后執行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("------------處理后------------");
}
//在dispatcherServlet處理后執行,做清理工作.
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("------------清理------------");
}
}
12345678910111213141516171819202122232425
? ? > 在 a p p l i c a t i o n C o n t e x t . x m l 中 配 置 攔 截 器 \textcolor{OrangeRed}{--> 在applicationContext.xml中配置攔截器}??>在applicationContext.xml中配置攔截器
<!--關于攔截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--/** 包括路徑及其子路徑-->
<!--/admin/* 攔截的是/admin/add等等這種 , /admin/add/user不會被攔截-->
<!--/admin/** 攔截的是/admin/下的所有-->
<mvc:mapping path="/**"/>
<!--bean配置的就是攔截器-->
<bean class="com.hxl.config.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1234567891011
前面的我們都不動,運行,我們可以看到效果
那么接下來就用一個實例來體驗一下攔截器(登錄)
在WEB-INF下的所有頁面或者資源,只能通過controller或者servlet進行訪問
? ? > i n d e x . j s p \textcolor{OrangeRed}{--> index.jsp}??>index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/goLogin">登錄</a>
<a href="${pageContext.request.contextPath}/goMain">首頁</a>
</body>
</html>
123456789101112
? ? > m a i n . j s p \textcolor{OrangeRed}{--> main.jsp}??>main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
</body>
</html>
1234567891011
? ? > l o g i n . j s p \textcolor{OrangeRed}{--> login.jsp}??>login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄</title>
</head>
<body>
<h1>登錄頁面</h1>
<form action="${pageContext.request.contextPath}/login" method="post">
用戶名:<input type="text" name="username">
密碼:<input type="text" name="password">
<input type="submit" value="登錄">
</form>
</body>
</html>
12345678910111213141516
? ? > L o g i n C o n t r o l l e r \textcolor{OrangeRed}{--> LoginController}??>LoginController
package com.hxl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/goMain")
public String goMain(){
return "main";
}
@RequestMapping("/goLogin")
public String goLogin(){
return "login";
}
@RequestMapping("/login")
public String login(HttpSession session,String username,String password){
// 向session記錄用戶身份信息
System.out.println("接收前端==="+username);
session.setAttribute("user", username);
return "main";
}
}
12345678910111213141516171819202122232425262728
? ? > 測 試 : \textcolor{OrangeRed}{--> 測試:}??>測試:
因為在WEB-INF下的 頁面我們不能直接訪問,所以在index中進行跳轉,然后請求到login.jsp。此時我們在這里輸入,就會在session中攜帶賬號密碼。
但此時是不符合的,因為我們還沒有登錄就可以去首頁,所以我們需要寫一個攔截器的功能
? ? > i n d e x . j s p \textcolor{OrangeRed}{--> index.jsp}??>index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="${pageContext.request.contextPath}/user/goLogin">登錄</a>
<a href="${pageContext.request.contextPath}/user/goMain">首頁</a>
</body>
</html>
123456789101112
? ? > l o g i n . j s p \textcolor{OrangeRed}{--> login.jsp}??>login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄</title>
</head>
<body>
<h1>登錄頁面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用戶名:<input type="text" name="username">
密碼:<input type="text" name="password">
<input type="submit" value="登錄">
</form>
</body>
</html>
12345678910111213141516
? ? > m a i n . j s p \textcolor{OrangeRed}{--> main.jsp}??>main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<p>${username}</p>
<p>
<a href="${pageContext.request.contextPath}/user/goOut">注銷</a>
</p>
</body>
</html>
1234567891011121314
? ? > L o g i n I n t e r c e p t o r \textcolor{OrangeRed}{--> LoginInterceptor}??>LoginInterceptor
此時我們去寫一個登錄的攔截器,來判斷它到底什么時候進行攔截
package com.hxl.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session=request.getSession();
//放行:判斷什么情況下沒有登錄
//登錄頁面放行
if(request.getRequestURI().contains("goLogin")){
return true;
}
if(request.getRequestURI().contains("login")){
return true;
}
//用戶已登錄,第一次登錄的時候也是沒有session的。
if(session.getAttribute("user") !=null){
return true;
}
//判斷什么情況下沒有登錄
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}
}
12345678910111213141516171819202122232425262728
? ? > L o g i n C o n t r o l l e r \textcolor{OrangeRed}{--> LoginController}??>LoginController
這里面我們有一個類url,下面的請求都需要加上/user,在配置攔截器的時候可以加一個,只攔截user請求下的。以及注銷功能
package com.hxl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/user")
public class LoginController {
@RequestMapping("/goMain")
public String goMain(){
return "main";
}
@RequestMapping("/goLogin")
public String goLogin(){
return "login";
}
@RequestMapping("/login")
public String login(HttpSession session, String username, String password, Model model){
// 向session記錄用戶身份信息
System.out.println("接收前端==="+username);
session.setAttribute("user", username);
model.addAttribute("username",username);
return "main";
}
@RequestMapping("/goOut")
public String goOut(HttpSession session){
/*//銷毀,下面的好
session.invalidate();*/
//移除
session.removeAttribute("user");
return "main";
}
}
12345678910111213141516171819202122232425262728293031323334353637383940
? ? > a p p l i c a t i o n C o n t e x t . x m l \textcolor{OrangeRed}{-->applicationContext.xml}??>applicationContext.xml
<!--關于攔截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--/** 包括路徑及其子路徑-->
<!--/admin/* 攔截的是/admin/add等等這種 , /admin/add/user不會被攔截-->
<!--/admin/** 攔截的是/admin/下的所有-->
<mvc:mapping path="/**"/>
<!--bean配置的就是攔截器-->
<bean class="com.hxl.config.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<!--user下面的請求-->
<mvc:mapping path="/user/**"/>
<bean class="com.hxl.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1234567891011121314151617
? ? > 測 試 \textcolor{OrangeRed}{-->測試}??>測試
此時我們啟動之后,如果沒有登錄,那么會重定向到goLogin頁面,然后登錄,登錄之后跳轉到main頁面,其中有注銷功能,沒有注銷之前可以去index頁面點擊首頁正常跳轉,如果注銷了,session沒有了,那么就會跳轉到登錄頁面。
面介紹了Spring Boot 如何整合定時任務已經Spring Boot 如何創建異步任務,不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html。
接下來開始講 Spring Boot的重要功能:整合攔截器。以前我們在做mvc 項目時也使用到的是filter過濾器也就是攔截器。其實Spring Boot 中的攔截器和SpringMVC中的攔截器也是類似的,只是配置上有些區別。那么下面我們就來看看Spring Boot 是怎么配置攔截器的。
創建InterceptorConfig 攔截器配置類,這個類主要是統一配置管理所有的攔截器。
package com.weiz.config;
import com.weiz.controller.interceptor.TwoInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import com.weiz.controller.interceptor.OneInterceptor;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private OneInterceptor myInterceptor1;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加要攔截的url 1 攔截的路徑 放行的路徑
registry.addInterceptor(myInterceptor1).addPathPatterns("/admin/**").excludePathPatterns("/admin/login");
}
}
說明:
1、使用注解@Configuration配置攔截器
2、繼承WebMvcConfigurer 接口
3、重寫addInterceptors方法,添加需要的攔截器地址
前面創建了攔截器的配置管理類,接下來就應該創建具體的攔截器。首先創建com.weiz.controller.interceptor包,并創建OneInterceptor攔截器。這個攔截器通過實現 HandlerInterceptor 接口,達到請求攔截的作用。具體代碼如下:
package com.weiz.controller.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class OneInterceptor implements HandlerInterceptor {
/**
* 在請求處理之前進行調用(Controller方法調用之前)
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object object) throws Exception {
System.out.println("被OneInterceptor攔截,放行...");return true;
}
/**
* 請求處理之后進行調用,但是在視圖被渲染之前(Controller方法調用之后)
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object object, ModelAndView mv)
throws Exception {
// TODO Auto-generated method stub
}
/**
* 在整個請求結束之后被調用,也就是在DispatcherServlet 渲染了對應的視圖之后執行
* (主要是用于進行資源清理工作)
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object object, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
說明:
1、HandlerInterceptor接口有 3 個攔截方法:
preHandle:Controller邏輯執行之前進行攔截
postHandle:Controller邏輯執行完畢但是視圖解析器還未進行解析之前進行攔截
afterCompletion:Controller邏輯和視圖解析器執行完畢進行攔截
2、實際開發中 一般preHandle 使用頻率比較高,postHandle 和 afterCompletion操作相對比較少。
postHandle 是在視圖解析前進行攔截,通過 Model 再次添加數據到 Request域中。
afterCompletion 暫時沒有想到使用場景,如果有使用過的場景可以在下面評論區中進行評論。
在瀏覽器中,輸入配置管理器中攔截的地址:http://localhost:8088/th/index
從上圖可以看出定義的攔截器生效了,打印出了攔截器里面的log 。
以上,就把Spring Boot 如何使用攔截器介紹完了,是不是特別簡單。SpringBoot 2 整合攔截器和整合 Filter的操作很像,都是通過一個注冊類將其注入到Spring的上下文中,只不過Filter使用的是 FilterRegistrationBean 而攔截器使用的是 InterceptorRegistry。
個人覺得比使用 xml 配置的方式更為簡單,如果你還沒有在 SpringBoot 項目中使用過攔截器,趕快來操作一下吧!
這個系列課程的完整源碼,也會提供給大家。大家關注我的頭條號(章為忠學架構),獲取這個系列課程的完整源碼。
Spring Boot整合定時任務Task,一秒搞定定時任務
SpringBoot入門系列(四)如何整合Thymeleaf模板引擎
Spring Boot集成Redis代碼詳解,三步搞定!
SpringBoot入門系列(三)資源文件屬性配置
Spring Boot入門系列(六)Spring整合Mybatis詳解「附詳細步驟」
springmvc攔截器是我們項目開發中用到的一個功能,常常用于對Handler進行預處理和后處理。本案例來演示一個較簡單的springmvc攔截器的使用,并通過分析源碼來探究攔截器的執行順序是如何控制的。
? 該步驟不再截圖說明
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
? 配置springmvc核心控制器DispatcherServlet,由于需要加載springmvc.xml,所以需要創建一個springmvc.xml文件(文件參考源碼附件)放到classpath下
<!-- 前端控制器(加載classpath:springmvc.xml 服務器啟動創建servlet) -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置初始化參數,創建完DispatcherServlet對象,加載springmvc.xml配置文件 -->
<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>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
? 兩個攔截器分別命名為MyInterceptor1、MyInterceptor2
public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==1-1====前置攔截器1 執行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==1-2=====后置攔截器1 執行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==1-3======最終攔截器1 執行======");
}
}public class MyInterceptor2 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("==2-1====前置攔截器2 執行======");
return true; //ture表示放行
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("==2-2=====后置攔截器2 執行======");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("==2-3======最終攔截器2 執行======");
}
}
<!--配置攔截器-->
<mvc:interceptors>
<!--配置攔截器-->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.itheima.interceptor.MyInterceptor1" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.itheima.interceptor.MyInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
這兩個攔截器攔截規則相同,并且配置順序攔截器1在攔截器2之前!
@Controller
public class BizController {
@RequestMapping("testBiz")
public String showUserInfo(Integer userId, Model model){
System.out.println(">>>>>業務代碼執行-查詢用戶ID為:"+ userId);
User user=new User(userId);
user.setName("宙斯");
model.addAttribute("userInfo",user);
return "user_detail";
}
}
該controller會轉發到user_detail.jsp頁面
<html>
<head>
<title>detail</title>
</head>
<body>
用戶詳情:
${userInfo.id}:${userInfo.name}
<%System.out.print(">>>>>jsp頁面的輸出為:");%>
<%System.out.println(((User)request.getAttribute("userInfo")).getName());%>
</body>
</html>
? 啟動項目后,在地址欄訪問/testBiz?userId=1,然后查看IDE控制臺打印:
==1-1====前置攔截器1 執行========2-1====前置攔截器2 執行======>>>>>業務代碼執行-查詢用戶ID為:1==2-2=====后置攔截器2 執行========1-2=====后置攔截器1 執行======>>>>>jsp頁面的輸出為:宙斯==2-3======最終攔截器2 執行========1-3======最終攔截器1 執行======
通過打印日志發現,攔截器執行順序是: 攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
? 經過測試發現攔截器執行順序如下:
攔截器1的前置>攔截器2的前置>業務代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終
我們通過分析源碼來探究下攔截器是如何執行的
? 當瀏覽器發送/testBiz?userId=1的請求時,會經過DispatcherServlet的doDispatch方法,我們將其取出并觀察其核心代碼(省略非關鍵代碼)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
try {
try {
ModelAndView mv=null;
Object dispatchException=null;
try {
processedRequest=this.checkMultipart(request);
multipartRequestParsed=processedRequest !=request;
//1.獲取執行鏈
mappedHandler=this.getHandler(processedRequest);
if (mappedHandler==null) {
this.noHandlerFound(processedRequest, response);
return;
}
//2.獲取處理器適配器
HandlerAdapter ha=this.getHandlerAdapter(mappedHandler.getHandler());
//...
//【3】.執行前置攔截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//4.執行業務handler
mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//【5】.執行后置攔截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException=var20;
} catch (Throwable var21) {
dispatchException=new NestedServletException("Handler dispatch failed", var21);
}
//【6】.處理頁面響應,并執行最終攔截器
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
}finally {
//...
}
}
代碼中有關攔截器執行的位置我都添加了注釋,其中注釋中標識的步驟中,3、5、6步驟是攔截器的關鍵步驟
其中,第一步中"獲取執行鏈",執行鏈內容可以通過debug調試查看內容:
可以看到我們自定義的兩個攔截器按順序保存
? 在doDispatch方法中,我們添加的注釋的第【3】、【5】、【6】步驟是對攔截器的執行處理,現在分別來查看第【3】、【5】、【6】步驟執行的具體方法的源碼
//3.執行前置攔截器中的詳細代碼
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//獲得本次請求對應的所有攔截器
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按照攔截器順序依次執行每個攔截器的preHandle方法.
//并且,interceptorIndex值會一次 + 1 (該值是給后面的最終攔截器使用的)
for(int i=0; i < interceptors.length; this.interceptorIndex=i++) {
HandlerInterceptor interceptor=interceptors[/color][i][color=black];
//只要每個攔截器不返回false,則繼續執行,否則執行最終攔截器
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
//最終返回true
return true;
}
? 我們可以看到攔截器的preHandler(前置處理)方法是按攔截器(攔截器1、攔截器2)順序執行的,然后我們再來看步驟【5】
//5.執行后置攔截器
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
//獲得本次請求對應的所有攔截器
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//按倒敘執行每個攔截器的postHandle方法——所以我們看到先執行的攔截器2的postHandle,再執行攔截器1的postHandle
for(int i=interceptors.length - 1; i >=0; --i) {
HandlerInterceptor interceptor=interceptors[/color][color=black];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
會發現,后置處理是按照攔截器順序倒敘處理的!
? 我們最后來看下最終攔截器
//執行的方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
//...
if (mv !=null && !mv.wasCleared()) {
//處理響應
this.render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler !=null) {
//6、執行攔截器的最終方法們
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
其中,有一個render()方法,該方法會直接處理完response。再后則是觸發triggerAfterCompletion方法:
//6、執行攔截器的最終方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
HandlerInterceptor[] interceptors=this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
//倒敘執行每個攔截器(interceptorIndex為前置攔截器動態計算)的afterCompletion方法
for(int i=this.interceptorIndex; i >=0; --i) {
HandlerInterceptor interceptor=interceptors[/color][/i][color=black][i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
由此可以看到,攔截器的最終方法的執行也是按照倒敘來執行的,而且是在響應之后。
? 攔截器常用于初始化資源,權限監控,會話設置,資源清理等的功能設置,就需要我們對它的執行順序完全掌握,我們通過源碼可以看到,攔截器類似于對我們業務方法的環繞通知效果,并且是通過循環收集好的攔截器集合來控制每個攔截器方法的執行順序,進而可以真正做到深入掌握攔截器的執行機制!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。