SpringMVC攔截器


SpringMVC攔截器詳解

目錄

前言

SpringMVC是目前主流的Web MVC框架之一。 

如果有同學對它不熟悉,那么請參考它的入門blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

攔截器是每個Web框架必備的功能,也是個老生常談的主題了。

本文將分析SpringMVC的攔截器功能是如何設計的,讓讀者了解該功能設計的原理。

重要接口及類介紹

1. HandlerExecutionChain類

  由HandlerMethod和Interceptor集合組成的類,會被HandlerMapping接口的getHandler方法獲取。

2. HandlerInterceptor接口

  

  SpringMVC攔截器基礎接口。 

3. AbstractHandlerMapping

  HandlerMapping的基礎抽象類。

4. AsyncHandlerInterceptor

  繼承HandlerInterceptor的接口,額外提供了afterConcurrentHandlingStarted方法,該方法是用來處理異步請求。當Controller中有異步請求方法的時候會觸發該方法。 樓主做過測試,異步請求先支持preHandle、然后執行afterConcurrentHandlingStarted。異步線程完成之后執行preHandle、postHandle、afterCompletion。 有興趣的讀者可自行研究。

5. HandlerInterceptorAdapter

   實現AsyncHandlerInterceptor接口的抽象類,一般我們使用攔截器的話都會繼承這個類。然后復寫相應的方法。

6. WebRequestInterceptor

  與HandlerInterceptor接口類似,區別是WebRequestInterceptor的preHandle沒有返回值。還有WebRequestInterceptor是針對請求的,接口方法參數中沒有response。

  

  AbstractHandlerMapping內部的interceptors是個Object類型集合。處理的時候判斷為MappedInterceptor[加入到mappedInterceptors集合中];HandlerInterceptor、WebRequestInterceptor(適配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]

7. MappedInterceptor

  一個包括includePatterns和excludePatterns字符串集合並帶有HandlerInterceptor的類。 很明顯,就是對於某些地址做特殊包括和排除的攔截器。

   

8. ConversionServiceExposingInterceptor

  默認的<annotation-driven/>標簽初始化的時候會初始化ConversionServiceExposingInterceptor這個攔截器,並被當做構造方法的參數來構造MappedInterceptor。之后會被加入到AbstractHandlerMapping的mappedInterceptors集合中。該攔截器會在每個請求之前往request中丟入ConversionService。主要用於spring:eval標簽的使用。

源碼分析

首先我們看下攔截器的如何被調用的。

Web請求被DispatcherServlet截獲后,會調用DispatcherServlet的doDispatcher方法。

很明顯地看到,在HandlerAdapter處理之后,以及處理完成之后會調用HandlerExecutionChain的方法。

HandlerExecutionChain的applyPreHandle、applyPostHandle、triggerAfterCompletion方法如下:

很明顯,就是調用內部實現HandlerInterceptor該接口集合的各個對應方法。

 

下面我們看下HandlerExecutionChain的構造過程。

 HandlerExecutionChain是從HandlerMapping接口的getHandler方法獲取的。

 HandlerMapping的基礎抽象類AbstractHandlerMapping中:

我們看到,HandlerExecutionChain的攔截器是從AbstractHandlerMapping中的adaptedInterceptors和mappedInterceptors屬性中獲取的。

攔截器的配置

清楚了HandlerExecutionChain的攔截器屬性如何構造之后,下面來看下SpringMVC是如何配置攔截器的。

1. *-dispatcher.xml配置文件中添加 <mvc:interceptors>配置

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/login"/>   
    <mvc:exclude-mapping path="/index"/>
    <bean class="package.interceptor.XXInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

這里配置的每個<mvc:interceptor>都會被解析成MappedInterceptor。

其中子標簽<mvc:mapping path="/**"/>會被解析成MappedInterceptor的includePatterns屬性;<mvc:exclude-mapping path="/**"/>會被解析成MappedInterceptor的excludePatterns屬性;<bean/>會被解析成MappedInterceptor的interceptor屬性。

<mvc:interceptors>這個標簽是被InterceptorsBeanDefinitionParser類解析。

2. 配置RequestMappingHandlerMapping,並配置該bean對應的interceptors集合屬性。 這里的interceptors集合是個Object類型的泛型集合。

  AbstractHandlerMapping抽象類只暴露了1個攔截器的set方法 -> interceptors。

  adaptedInterceptors和mappedInterceptors均沒有暴露set方法,因此我們只能為RequestMappingHandlerMapping配置interceptors屬性。

  其實AbstractHandlerMapping內部的initInterceptors方法中,會遍歷interceptors集合,然后判斷各個項是否是MappedInterceptor、HandlerInterceptor、WebRequestInterceptor。

  其中MappedInterceptor類型的攔截器會被加到mappedInterceptors集合中,HandlerInterceptor類型的會被加到adaptedInterceptors集合中,WebRequestInterceptor類型的會被適配成WebRequestHandlerInterceptorAdapter加到adaptedInterceptors集合中。

  

  

  配置如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
  <property name="interceptors">
    <bean class="package.interceptor.XXInterceptor"/>
  </property>  
  <property name="order" value="-1"/>
</bean>

  這里有個屬性order,配成了-1。 為什么呢?

 

  其實是個小知識,與讀者分享一下。

  Spring提供了一個Order接口,主要是排序用的。

  

  我們這里使用的AbstractHandlerMapping抽象類也實現了這個接口。

  DispatcherServlet初始化HandlerMapping集合的代碼:

  因此,如果讀者又配置了:

  <mvc:annotation-driven/>

  那么就會有2只HandlerMapping,所以我們配置RequestMappingHandlerMapping的時候設置order為-1,就是為了讓這個HandlerMapping的優先級高於<mvc:annotation-driven/>配置的RequestMappingHandlerMapping級別。

  如果讀者只配置了RequestMappingHandlerMapping,沒配置<mvc:annotation-driven/>。 那么可以忽略這個小知識~~~~~~~~ 

 

一般建議使用第一種方法。 

編寫自定義的攔截器

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";
    }
  
}

 *-diapatcher.xml配置:

<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="org.format.demo.interceptor.LoginInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

 

PS:我們看到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"/>

總結

總結了SpringMVC攔截器的原理以及各種配置,像網上很多人會問為什么攔截器執行preHandle方法返回false之后還是會執行afterCompletion方法,其實我們看下源碼就知道了。

關於異步請求方面的攔截器以及第二種配置方法(interceptors集合屬性可加入繼承自HandlerInterceptorAdapter抽象類的類以及實現WebRequestInterceptor接口的類),讀者可自行研究。

 

文中難免有錯誤,希望讀者能夠指出來。

 
 
分類:  java
標簽:  javaspringspringmvcinterceptor


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM