首頁技術(shù)文章正文

springmvc攔截器及源碼分析【技術(shù)點干貨】

更新時間:2021-03-26 來源:黑馬程序員 瀏覽量:

1577370495235_學(xué)IT就到黑馬程序員.gif


springmvc攔截器是我們項目開發(fā)中用到的一個功能,常常用于對Handler進行預(yù)處理和后處理。本案例來演示一個較簡單的springmvc攔截器的使用,并通過分析源碼來探究攔截器的執(zhí)行順序是如何控制的。

1、springmvc攔截器使用


1.1 項目初始搭建


1.1.1 創(chuàng)建一個maven的war工程

該步驟不再截圖說明

1.1.2 引入maven依賴

<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>


1.2.3 配置web.xml

配置springmvc核心控制器DispatcherServlet,由于需要加載springmvc.xml,所以需要創(chuàng)建一個springmvc.xml文件(文件參考源碼附件)放到classpath下

<!-- 前端控制器(加載classpath:springmvc.xml 服務(wù)器啟動創(chuàng)建servlet) -->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置初始化參數(shù),創(chuàng)建完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>

1.2 攔截器開發(fā)

1.2.1 準備兩個攔截器

兩個攔截器分別命名為MyInterceptor1、MyInterceptor2

public class MyInterceptor1 implements HandlerInterceptor {
     
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("==1-1====前置攔截器1 執(zhí)行======");
        return true; //ture表示放行
    }
 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("==1-2=====后置攔截器1 執(zhí)行======");
    }
 
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("==1-3======最終攔截器1 執(zhí)行======");
    }
}
public class MyInterceptor2 implements HandlerInterceptor {
 
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("==2-1====前置攔截器2 執(zhí)行======");
        return true; //ture表示放行
    }
 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("==2-2=====后置攔截器2 執(zhí)行======");
    }
 
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("==2-3======最終攔截器2 執(zhí)行======");
    }
}


1.2.2 在springmvc.xml中攔截器

<!--配置攔截器-->
<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>

這兩個攔截器攔截規(guī)則相同,并且配置順序攔截器1攔截器2之前!


1.3 測試攔截器效果

1.3.1 準備測試Controller

@Controller
public class BizController {
    @RequestMapping("testBiz")
    public String showUserInfo(Integer userId, Model model){
        System.out.println(">>>>>業(yè)務(wù)代碼執(zhí)行-查詢用戶ID為:"+ userId);
        User user = new User(userId);
        user.setName("宙斯");
        model.addAttribute("userInfo",user);
        return "user_detail";
    }
}

該controller會轉(zhuǎn)發(fā)到user_detail.jsp頁面

1.3.2 準備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>

1.3.3 測試效果

啟動項目后,在地址欄訪問/testBiz?userId=1,然后查看IDE控制臺打印:

==1-1====前置攔截器1 執(zhí)行======
==2-1====前置攔截器2 執(zhí)行======
>>>>>業(yè)務(wù)代碼執(zhí)行-查詢用戶ID為:1
==2-2=====后置攔截器2 執(zhí)行======
==1-2=====后置攔截器1 執(zhí)行======
>>>>>jsp頁面的輸出為:宙斯
==2-3======最終攔截器2 執(zhí)行======
==1-3======最終攔截器1 執(zhí)行======

通過打印日志發(fā)現(xiàn),攔截器執(zhí)行順序是:

攔截器1的前置>攔截器2的前置>業(yè)務(wù)代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終

 


2、源碼分析

經(jīng)過測試發(fā)現(xiàn)攔截器執(zhí)行順序如下:

攔截器1的前置>攔截器2的前置>業(yè)務(wù)代碼>攔截器2后置>攔截器1后置>攔截器2最終>攔截器1最終

我們通過分析源碼來探究下攔截器是如何執(zhí)行的

2.1 DispatcherServlet

當瀏覽器發(fā)送/testBiz?userId=1的請求時,會經(jīng)過DispatcherServletdoDispatch方法,我們將其取出并觀察其核心代碼(省略非關(guān)鍵代碼)

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.獲取執(zhí)行鏈
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //2.獲取處理器適配器
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                //...
                //【3】.執(zhí)行前置攔截器
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                //4.執(zhí)行業(yè)務(wù)handler
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                this.applyDefaultViewName(processedRequest, mv);
                //【5】.執(zhí)行后置攔截器
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
            //【6】.處理頁面響應(yīng),并執(zhí)行最終攔截器
            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 {
        //...
    }
}

代碼中有關(guān)攔截器執(zhí)行的位置我都添加了注釋,其中注釋中標識的步驟中,3、5、6步驟是攔截器的關(guān)鍵步驟

 

其中,第一步中"獲取執(zhí)行鏈",執(zhí)行鏈內(nèi)容可以通過debug調(diào)試查看內(nèi)容:

1616739961365_001.png

可以看到我們自定義的兩個攔截器按順序保存

2.2 攔截器步驟解析

doDispatch方法中,我們添加的注釋的第【3】、【5】、【6】步驟是對攔截器的執(zhí)行處理,現(xiàn)在分別來查看第【3】、【5】、【6】步驟執(zhí)行的具體方法的源碼

2.2.1 第【3】步驟

//3.執(zhí)行前置攔截器中的詳細代碼
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //獲得本次請求對應(yīng)的所有攔截器
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        //按照攔截器順序依次執(zhí)行每個攔截器的preHandle方法.
        //并且,interceptorIndex值會一次 + 1 (該值是給后面的最終攔截器使用的)
        for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = interceptors[/color][i][color=black];
            //只要每個攔截器不返回false,則繼續(xù)執(zhí)行,否則執(zhí)行最終攔截器
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
    }
    //最終返回true
    return true;
}

我們可以看到攔截器的preHandler(前置處理)方法是按攔截器(攔截器1、攔截器2)順序執(zhí)行的,然后我們再來看步驟【5】

2.2.2 第【5】步驟

//5.執(zhí)行后置攔截器
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    //獲得本次請求對應(yīng)的所有攔截器
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        //按倒敘執(zhí)行每個攔截器的postHandle方法——所以我們看到先執(zhí)行的攔截器2的postHandle,再執(zhí)行攔截器1的postHandle
        for(int i = interceptors.length - 1; i >= 0; --i) {
            HandlerInterceptor interceptor = interceptors[/color][color=black];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

會發(fā)現(xiàn),后置處理是按照攔截器順序倒敘處理的!

我們最后來看下最終攔截器

2.2.3 第【6】步驟

//執(zhí)行的方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    //...
 
    if (mv != null && !mv.wasCleared()) {
        //處理響應(yīng)
        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、執(zhí)行攔截器的最終方法們
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }
 
    }
}

其中,有一個render()方法,該方法會直接處理完response。再后則是觸發(fā)triggerAfterCompletion方法:

//6、執(zhí)行攔截器的最終方法
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //倒敘執(zhí)行每個攔截器(interceptorIndex為前置攔截器動態(tài)計算)的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);
                }
            }
        }
    }

由此可以看到,攔截器的最終方法的執(zhí)行也是按照倒敘來執(zhí)行的,而且是在響應(yīng)之后。

 

3、總結(jié)

攔截器常用于初始化資源,權(quán)限監(jiān)控,會話設(shè)置,資源清理等的功能設(shè)置,就需要我們對它的執(zhí)行順序完全掌握,我們通過源碼可以看到,攔截器類似于對我們業(yè)務(wù)方法的環(huán)繞通知效果,并且是通過循環(huán)收集好的攔截器集合來控制每個攔截器方法的執(zhí)行順序,進而可以真正做到深入掌握攔截器的執(zhí)行機制!



猜你喜歡:

BUG是什么意思?只有“漏洞”這一個意思嗎?

ASCII碼對照表【黑馬程序員】

依賴注入是什么?依賴注入介紹

Spring Cloud Hystrix原理詳細介紹

黑馬程序員高級java軟件開發(fā)工程師課程

分享到:
在線咨詢 我要報名
和我們在線交談!