Spring = Spring攔截器之實現原理?

(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=""/>,分別是作用路徑和不作用路徑。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章