SpringMVC攔截器詳解

本文作者:鐘昕靈,叩丁狼高級講師。原創文章,轉載請注明出處。

前言

Spring MVC屬于SpringFrameWork的后續產品,已經融合在Spring Web Flow里面。Spring 框架提供了構建 Web 應用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構,從而在使用Spring進行WEB開發時,可以選擇使用Spring的SpringMVC框架或集成其他MVC開發框架,如Struts1(現在一般不用),Struts2(一般老項目使用)等。

SpringMVC中的Interceptor攔截器用于攔截Controller層接口,表現形式有點像Spring的AOP,但是AOP是針對單一的方法。Interceptor是針對Controller接口以及可以處理request和response對象。

下面,我們來看看SpringMVC中攔截器的使用及實現

HandlerInterceptor接口的定義

在該接口中,定義了一下三個方法

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;

    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

  • preHandle:
    在訪問到達Controller之前執行,如果需要對請求做預處理,可以選擇在該方法中完成
    返回值為true:繼續執行后面的攔截器或者Controller
    返回值為false:不再執行后面的攔截器和Controller,并調用返回true的攔截器的afterCompletion方法

  • postHandle:
    在執行完Controller方法之后,渲染視圖之前執行,如果需要對響應相關的數據進行處理,可以選擇在該方法中完成

  • afterCompletion:
    調用完Controller接口,渲染View頁面后調用。返回true的攔截器都會調用該攔截器的afterCompletion方法,順序相反。

自定義攔截器和使用

自定義一個我們自己的攔截器非常簡單,定義一個類,實現上面的接口,然后覆寫對應的方法即可
當然,在實際開發中,我們一般選擇繼承該接口的實現類HandlerInterceptorAdapter來實現攔截器的定義,如下:

public class CheckLoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //1:獲取登錄憑證
        Employee emp  = UserContext.getCurrentUser();
        if(emp == null){
            //沒有登錄
            response.sendRedirect("/login.html");
            return false; //終止請求繼續往下執行
        }
        return true; //放行
    }
}

上面,我們定義了一個檢查用戶是否登錄的攔截器,如果沒有登錄,跳轉到登錄頁面,反之,放行繼續訪問目標資源

要讓我們的攔截器被框架得知并管理,我們還需要在配置文件中做如下配置來注冊攔截器

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/login.do"/>
            <mvc:exclude-mapping path="/login.html"/>
            <bean class="cn.wolfcode.rbac.web.interceptor.CheckLoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

如此,攔截器就能夠在項目中部署起來

當用戶發起請求相應資源的時候,會首先經過該攔截器的處理,防止用戶在沒有登錄的情況下直接訪問項目中的核心資源

原理解析

可以看出,攔截器在SpringMVC框架中實現是非常簡單的,但是,大家一定要清楚一個道理

當你覺得很輕松的時候,是有另外一些人替你負重前行

這個時候,是誰在為我們負重前行呢?當然是我們使用的SpringMVC框架了!

那么,框架這個時候都為我們做了哪些事情呢?請往下看:

SpringMVC框架的入口是一個使用Servlet實現的前端控制器:

  • DispatcherServlet

我們的每次請求都會先經過這個入口的處理才能到達目標資源,所以,來看看這里都做了哪些事吧

該類中,最重要的一個方法是doDispatch,在這個方法中,完成了整個執行流程的任務分配

下面是該方法中的部分核心代碼:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                //返回 HandlerExecutionChain  其中包含了攔截器隊列
                mappedHandler = this.getHandler(processedRequest);

                if(mappedHandler == null || mappedHandler.getHandler() == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }

                //獲取到適合處理當前請求的適配器,最終用來調用Controller中的方法
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                //調用攔截器鏈中所有攔截器的preHandle方法
                if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                    //如果有攔截器的preHandle方法返回值為false,則結束該方法的執行
                    return;
                }
                 //調用請求的Controller中的方法,獲取到ModelAndView對象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                 //調用攔截器鏈中所有攔截器的postHandle方法,和執行preHandle方法的順序相反
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var19) {
                dispatchException = var19;
            }
            //處理視圖渲染
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception var20) {
            //如果在執行過程中有異常,執行后續的收尾工作,執行對應攔截器中的afterCompletion方法
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
        } 
    }

  1. mappedHandler = this.getHandler(processedRequest);
    返回 HandlerExecutionChain 其中包含了攔截器隊列

  2. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    獲取到適合處理當前請求的適配器,最終用來調用Controller中的方法

  3. mappedHandler.applyPreHandle(processedRequest, response)
    調用攔截器鏈中所有攔截器的preHandle方法

  4. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    調用請求的Controller中的方法,獲取到ModelAndView對象

  5. mappedHandler.applyPostHandle(processedRequest, response, mv);
    調用攔截器鏈中所有攔截器的postHandle方法,和執行preHandle方法的順序相反

  6. this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    處理結果視圖的渲染,簡單說就是頁面的跳轉問題

  7. this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
    如果在執行過程中有異常,執行后續的收尾工作,執行對應攔截器中的afterCompletion方法

通過上面對DispatcherServlet中核心代碼的分析,相信大家對攔截器的執行流程有了大致的理解

下面我們再對這個過程中的細節繼續進行分析:

  • 獲取攔截器
    DispatcherServlet:

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          HandlerExecutionChain handler;
              handler = hm.getHandler(request);
          return handler;
      }
    
    

    AbstractHandlerMapping:

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          Object handler = this.getHandlerInternal(request);
          if(handler == null) {
              handler = this.getDefaultHandler();
          }
          if(handler == null) {
              return null;
          } else {
              HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
              return executionChain;
          }
      }
    
    

    AbstractHandlerMapping:
    遍歷所有的攔截器, 把所有匹配當前請求的所有攔截添加到攔截器隊列中

      protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
          HandlerExecutionChain chain = handler instanceof HandlerExecutionChain?(HandlerExecutionChain)handler:new HandlerExecutionChain(handler);
          String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
          Iterator var5 = this.adaptedInterceptors.iterator();
          while(var5.hasNext()) {
              HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
              if(interceptor instanceof MappedInterceptor) {
                  MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
                  if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                      chain.addInterceptor(mappedInterceptor.getInterceptor());
                  }
              } else {
                  chain.addInterceptor(interceptor);
              }
          }
    
          return chain;
      }
    
    

    MappedInterceptor:
    <mvc:exclude-mapping path="/login.html"/> 如果請求資源路徑為 /login.html 則排除當前攔截器
    <mvc:mapping path="/"/>如果請求資源路徑為 不在exclude-mapping中,且能夠匹配 / 路徑, 則添加到攔截器隊列

    public boolean matches(String lookupPath, PathMatcher pathMatcher) {
          PathMatcher pathMatcherToUse = this.pathMatcher != null?this.pathMatcher:pathMatcher;
          String[] var4;
          int var5;
          int var6;
          String pattern;
          if(this.excludePatterns != null) {
              var4 = this.excludePatterns;
              var5 = var4.length;
    
              for(var6 = 0; var6 < var5; ++var6) {
                  pattern = var4[var6];
                  if(pathMatcherToUse.match(pattern, lookupPath)) {
                      return false;
                  }
              }
          }
    
          if(this.includePatterns == null) {
              return true;
          } else {
              var4 = this.includePatterns;
              var5 = var4.length;
    
              for(var6 = 0; var6 < var5; ++var6) {
                  pattern = var4[var6];
                  if(pathMatcherToUse.match(pattern, lookupPath)) {
                      return true;
                  }
              }
    
              return false;
          }
      }
    
    
  • 處理攔截器

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
          HandlerInterceptor[] interceptors = this.getInterceptors();
          if(!ObjectUtils.isEmpty(interceptors)) {
              for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                  HandlerInterceptor interceptor = interceptors[i];
                  //調用攔截器中的preHandle方法
                  if(!interceptor.preHandle(request, response, this.handler)) {
                      // 如果preHandler方法返回false,則觸發afterCompletion方法的執行
                      this.triggerAfterCompletion(request, response, (Exception)null);
                      return false;
                  }
              }
          }
    
          return true;
      }
    
    
      void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
          HandlerInterceptor[] interceptors = this.getInterceptors();
          if(!ObjectUtils.isEmpty(interceptors)) {
              for(int i = interceptors.length - 1; i >= 0; --i) {
                  HandlerInterceptor interceptor = interceptors[i];
                  //調用攔截器中的postHandle方法
                  interceptor.postHandle(request, response, this.handler, mv);
              }
          }
    
      }
    
    

    當前攔截器中preHandle方法如果返回true,則該方法會在下面幾種情況的時候會執行

①Controller正常執行,視圖渲染后
②程序有異常的時候
③在任何攔截器preHandle方法返回false的時候

   void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = this.interceptorIndex; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    //調用攔截器中的afterCompletion方法
                    interceptor.afterCompletion(request, response, this.handler, ex);
                } catch (Throwable var8) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
                }
            }
        }

    }

可以看到,SpringMVC在執行這一系列的處理的時候,做了很多的細節處理,但是,在我們看源碼的時候最好能夠排除和功能無關的代碼,這樣有利于我們理解整個執行流程

所以,在上面的代碼中,我僅僅將這個過程中比較重要的代碼貼了出來, 不是完整的代碼,如果要看完成的代碼,請自行參考框架的源碼學習,謝謝

想獲取更多技術視頻,請前往叩丁狼官網:http://www.wolfcode.cn/openClassWeb_listDetail.html

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容