(1)SpringMVC運行流程
在介紹SpringMVC攔截器之前,我們先介紹一下SpringMVC的運行流程:
(1)用戶發送請求,經過 前端控制器Dispacherservlet(Controller的核心)將url交給處理器映射器HandlerMapping處理
(2)處理器映射器HandlerMapping處理url,返回HandlerExecutionChain(可能包含攔截器,一定包含自定義的Controller(Handler))
(3)前端控制器Controller交給處理器適配器HandlerAdapter 處理,處理完成後,返回MV對象(ModelAndView)
(4)前端控制器將MV 交給視圖控制器ViewResolver ,處理過程:將MV 拆分成Model和View 兩個對象,並且將model渲染到View視圖上,並且將View返回給前端控制器
(5)最後,前端控制器將視圖響應給用戶。
(2)SpringMVC攔截器:
Spring MVC 的處理器攔截器類似於 Servlet 開發中的過濾器 Filter,用於對處理器進行預處理和後處理。
作用:用戶可以自己定義一些攔截器來實現特定的功能。例:訪問特定頁面前驗證用戶是否登陸等
攔截器鏈:
談到攔截器,還要向大家提一個詞——攔截器鏈(Interceptor Chain)。攔截器鏈就是將攔截器按一定的順序聯結成一條鏈。在訪問被攔截的方法或字段時,攔截器鏈中的攔截器就會按其之前定義的順序被調用。
攔截器與過濾器的區別:
它和過濾器是有幾分相似,但是也有區別,接下來我們就來說說他們的區別:
1)、過濾器是 servlet 規範中的一部分, 任何 java web 工程都可以使用。
攔截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
2)、過濾器在 url-pattern 中配置了/*之後,可以對所有要訪問的資源攔截。
攔截器它是隻會攔截訪問的控制器方法,如果訪問的是 jsp, html,css,image 或者 js 是不會進行攔截的。
攔截器鏈執行流程圖:
SpringMVC自定義攔截器使用步驟:
(1)自定義攔截器:
SpringMVC爲我們提供了攔截器規範的接口,創建一個類實現HandlerInterceptor ,重寫接口中的抽象方法;
抽象方法介紹:
1)、preHandle方法:在調用處理器之前調用該方法,如果該方法返回true則請求繼續向下進行,否則請求不會繼續向下進行,處理器也不會調用;
2)、postHandle方法:在調用完處理器後調用該方法;
3)、afterCompletion方法:只要該攔截器中的preHandle方法返回true,該方法就會被調用;
(2)在SpringMVC核心配置文件中註冊自定義攔截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用於指定對攔截的 url -->
<mvc:exclude-mapping path=""/><!-- 用於排除指定的 url-->
<!-- 使用指定的攔截器進行對指定的url進行攔截-->
<bean id="handlerInterceptorDemo1" class="攔截器所對應的全限定類名"></bean>
</mvc:interceptor>
</mvc:interceptors>
三.攔截器的實現方式:
SpringMVC攔截器的實現一般有兩種實現方式:
第一種是:要定義的 Interceptor 類要實現了Spring的HandlerInterceptor 接口
第二種是:繼承實現了HandlerInterceptor 接口的類,比如Spring已經提供的實現了 HandlerInterceptor 接口的抽象類HandlerInterceptorAdapter
注意上圖的步驟三,HandlerMapping接口,返回一個HandlerExecutionChain類、
HandlerExecutionChain是通過HandlerMapping的getHandler方法返回的。
繼承該接口的類是來實現請求和handler對象的映射關係的。
這個接口中只有這樣一個方法
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
根據函數名,參數及返回值我們不難猜出這個接口的作用,就是根據request返回HandlerExecutionChain。至於HandlerMapping在springMVC中有多種實現,我們此處就不深究了。
對於getHandler最後的調度部分便是springMVC的最外層DispatcherServlet類(Controller的核心)了。
HandlerExecutionChain類由HandlerInterceptor構成
HandlerExecutionChain類:由一個handler和若干的HandlerInterceptor構成。那麼這個類的作用就顯而易見了,就是將攔截器和handle組合*起來執行。就是對handle進行了包裝。
這個類中有幾個主要的方法:
1.applyPreHandle()看起,我們發現這個方法就是做的這樣一個工作,按照列表中interceptor的順序來執行它們的preHandle方法,直到有一個返回false。再看一下返回false後這個方法所做的工作,這時會調用triggerAfterCompletion方法,此時this.interceptorIndex指向上一個返回true的interceptor的位置,所以它會按逆序執行所有返回true的interceptor的afterCompletion方法。
2.applyPostHandle(),這個方法較爲簡單,就是按照逆序執行所有interceptor的postHandle方法。
3.triggerAfterCompletion()也是一樣,就是從最後一次preHandle成功的interceptor處逆序執行afterCompletion方法。
舉例:
那麼整個攔截器的處理過程我們便可以很清晰的分爲兩種情況:
一種是所有攔截器preHandle都返回true情況,另一種是有攔截器preHandle 返回false 的情況:
對於第一種情況:
那麼在DispatcherServlet中分別依次調用HandlerExecutionChain類中applyPreHandle、applyPostHandle和triggerAfterCompletion方法,
那麼所有方法的執行順序爲
A.pre -> B.pre -> C.pre -> D.pre
-> D.post -> C.post -> B.post -> A.post
-> D.after -> C.after -> B.after -> A.after
對於第二種情況:
我們不妨設C攔截器的preHandle返回爲false。
這時DispatcherServlet類調用HandlerExecutionChain類中applyPreHandle方法,然後由applyPreHandle調用triggerAfterCompletion方法
那麼執行情況如下:
A.pre -> B.pre -> C.pre ->
B.after -> A.after
也就是,指向上一個返回true的interceptor的位置(B位置),所以它會按逆序執行所有返回true的interceptor的afterCompletion方法。(也就是跳過了他們的postHandle方法)
四.案例:
1.繼承自HandlerInterceptorAdapter
編寫自定義的攔截器
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 獲得請求路徑的uri
String uri = request.getRequestURI();
// 判斷路徑是登出還是登錄驗證,是這兩者之一的話執行Controller中定義的方法
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
return true;
}
// 進入登錄頁面,判斷session中是否有key,有的話重定向到首頁,否則進入登錄界面
if(uri.endsWith("/login/") || uri.endsWith("/login")) {
if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
response.sendRedirect(request.getContextPath() + "/index");
} else {
return true;
}
}
// 其他情況判斷session中是否有key,有的話繼續用戶的操作
if(request.getSession() != null && request.getSession().getAttribute("loginUser") != null) {
return true;
}
// 最後的情況就是進入登錄頁面
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
登錄Controller:
@Controller
@RequestMapping(value = "/login")
public class LoginController {
@RequestMapping(value = {"/", ""})
public String index() {
return "login";
}
@RequestMapping("/auth")
public String auth(@RequestParam String username, HttpServletRequest req) {
req.getSession().setAttribute("loginUser", username);
return "redirect:/index";
}
@RequestMapping("/out")
public String out(HttpServletRequest req) {
req.getSession().removeAttribute("loginUser");
return "redirect:/login";
}
}
配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="org.format.demo.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
我們看到LoginInterceptor裏的preHandle方法對於地址“/login/auth”和"/login/out"不處理。
因此,可以寫點配置,少寫帶java代碼。在攔截器配置中添加2個exclude-mapping,並且去掉LoginInterceptor裏的
if(uri.endsWith("/login/auth") || uri.endsWith("/login/out")) {
return true;
}
配置新增:
<mvc:exclude-mapping path="/login/out"/>
<mvc:exclude-mapping path="/login/auth"/>
2.實現接口HandlerInterceptor
它的主要作用是攔截用戶的請求並進行相應的處理。比如通過它來進行權限驗證,或者是來判斷用戶是否登陸。
Spring MVC 的攔截器類似於Servlet中的攔截器!需要先定義一個類實現HandlerInterceptor接口。添加未實現的方法,在springmvc配置中配置,具體實現步驟如下:亂碼過濾
package com.lx.controller;
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 {
/**
* 該方法在目標方法之前被調用
* 若返回值爲true,則繼續調用後去的攔截器和目標方法
* 若返回值爲false,則不會再調用後續的攔截器和方法
* 可以考慮做權限.日誌,事務,等.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("text/htm;charset=utf-8");
System.out.println("ddddd");
return true;
}
/**
* 調用目標方法之後,但渲染視圖之前.
* 有modelAndView對象,可以對請求域中的屬性或視圖做出修改.
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
* 渲染視圖之後調用,釋放資源.
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
-boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在請求到達Handler之前,先執行這個前置處理方法.當該方法返回false時,請求直接返回,不會傳遞到鏈中的下一個攔截器,更不會傳遞到鏈尾的Handler,只有返回true時,請求才會向鏈中的下一個處理節點傳遞!
-
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView); 在相應已經被渲染後,執行該方法.
-
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); 在響應已經被渲染後,執行該方法!
配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.lx.controller.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
注意其中的mvc:interceptor標籤,子標籤下有<mvc:mapping path="" />,<mvc:exclude-mapping path=""/>,分別是作用路徑和不作用路徑。