springmvc拦截器是偶尔会用到的一个功能,本案例来演示一个较简单的springmvc拦截器的使用,并通过源码来分析拦截器的执行顺序的控制。
具体操作步骤为:
1、maven项目引入spring依赖
2、配置web.xml中的DispatcherServlet
3、准备两个拦截器,并在springmvc配置文件中进行配置管理
4、准备业务类,该类转发到一个JSP页面,并在页面做后台打印
5、测试发送请求到业务类,查看执行顺序
6、源码分析
7、总结以及代码附件
————————————————————————————————————————————————————————————
下面开始开发!
1、maven项目引入spring依赖

[XML] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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>

2、配置web.xml中的DispatcherServlet

[XML] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<!-- 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置初始化参数,创建完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>

3、准备两个拦截器,并在springmvc配置文件中进行配置管理
两个拦截器代码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class MyInterceptor1 implements HandlerInterceptor {
     
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("==1-1====前置拦截器1 执行======");
        return true; //ture表示放行
    }
 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("==1-2=====后置拦截器1 执行======");
    }
 
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("==1-3======最终拦截器1 执行======");
    }
}
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class MyInterceptor2 implements HandlerInterceptor {
 
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("==2-1====前置拦截器2 执行======");
        return true; //ture表示放行
    }
 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("==2-2=====后置拦截器2 执行======");
    }
 
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("==2-3======最终拦截器2 执行======");
    }
}

springmvc配置如下(此处只挑选拦截器配置,具体代码参见附件):

[XML] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!--配置拦截器-->
    <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>

注意配置顺序拦截器1先于拦截器2,并且都拦截所有的方法
4、准备业务类,该类转发到一个JSP页面,并在页面做后台打印

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
@Controller
public class BizController {
    @RequestMapping("testBiz")
    public String showUserInfo(Integer userId, Model model){
        System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);
        User user = new User(userId);
        user.setName("宙斯");
        model.addAttribute("userInfo",user);
        return "user_detail";
    }
}

响应的页面如下:

[HTML] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
<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>

5、测试发送请求到业务类,查看执行顺序
启动项目后,在浏览器中访问/testBiz?userId=1,会在后台打印如下内容:

[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
==1-1====前置拦截器1 执行======
==2-1====前置拦截器2 执行======
>>>>>业务代码执行-查询用户ID为:1
==2-2=====后置拦截器2 执行======
==1-2=====后置拦截器1 执行======
>>>>>jsp页面的输出为:宙斯
==2-3======最终拦截器2 执行======
==1-3======最终拦截器1 执行======

6、源码分析
通过打印结果会发现,执行顺序为:拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
我们可以通过源码来分析一下为何是该打印顺序。
最关键的为DispatcherServlet的核心方法——doDispatch。
当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServlet的doDispatch方法,省略掉部分代码,贴出如下(注释有说明):

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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.获取执行链
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //2.获取处理器适配器
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
 
                    //...
                    //3.执行前置拦截器
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    //4.执行业务handler
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    this.applyDefaultViewName(processedRequest, mv);
                    //5.执行后置拦截器
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                //6.处理页面响应,并执行最终拦截器
                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 {
            //...
        }
    }

其中,第一步中"获取执行链",执行链内容可以截图查看:
<ignore_js_op>
可以看到我们自定义的两个拦截器按顺序保存。
        在doDispatch方法中,我们注释的第3、5、6步骤是对拦截器的执行处理,现在分别来查看第3、5、6步骤的源码。
第三步骤源码如下:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
//3.执行前置拦截器中的详细代码
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获得本次请求对应的所有拦截器
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //按照拦截器顺序依次执行每个拦截器的preHandle方法.
            //并且,interceptorIndex值会一次 + 1 (该值是给后面的最终拦截器使用的)
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[/color][i][color=black];
                //只要每个拦截器不返回false,则继续执行,否则执行最终拦截器
                if (!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }
        //最终返回true
        return true;
    }

我们可以看到拦截器的preHandler方法是按拦截器顺序执行的。紧接着贴出第5步的后置拦截器源码:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
//5.执行后置拦截器
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        //获得本次请求对应的所有拦截器
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandle
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[/color][color=black];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

会发现,后置处理是按照拦截器顺序倒叙处理的。最后,我们贴出执行最终拦截器方法的代码:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    //...
 
    if (mv != null && !mv.wasCleared()) {
        //处理响应
        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、执行拦截器的最终方法们
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }
 
    }
}

其中,有一个render()方法,该方法会直接处理完response。再后则是触发triggerAfterCompletion方法:

[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
//6、执行拦截器的[align=left][color=#b00000]最终[/color][/align]方法
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            //倒叙执行每个拦截器(interceptorIndex为前置拦截器动态计算)的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);
                }
            }
        }
    }

由此可以看到,拦截器的最终方法的执行也是按照倒叙来执行的,而且是在响应之后。
7、总结
        我们可以从源码中看到,拦截器类似于对我们业务方法的环绕通知效果,并且是通过循环集合来控制每个拦截器方法的执行顺序。
源码如下: <ignore_js_op> mvc_interceptor.zip (13.49 KB, 下载次数: 106) 

更多免费学习资料可关注:itheimaGZ获取

springmvc拦截器入门及其执行顺序源码分析的更多相关文章

  1. Springboot中mybatis执行逻辑源码分析

    Springboot中mybatis执行逻辑源码分析 在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动 ...

  2. SpringMVC拦截器及多拦截器时的执行顺序

    本文链接:https://blog.csdn.net/itcats_cn/article/details/80371639拦截器的配置步骤 springmvc.xml中配置多个拦截器配置自定义拦截器并 ...

  3. struts2拦截器的实现原理及源码剖析

    拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理.同时,拦截器也可以让你将通用的 ...

  4. springmvc执行流程 源码分析

    进入DispatcherServlet 执行onRefresh,然后执行初始化方法initStrategies.然后调用doService——>doDispatch. 根据继承关系执行Servl ...

  5. SpringMVC类型转换、数据绑定详解[附带源码分析]

    目录 前言 属性编辑器介绍 重要接口和类介绍 部分类和接口测试 源码分析 编写自定义的属性编辑器 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那 ...

  6. springcloud 入门 5 (feign源码分析)

    feign:(推荐使用) Feign是受到Retrofit,JAXRS-2.0和WebSocket的影响,它是一个jav的到http客户端绑定的开源项目. Feign的主要目标是将Java Http ...

  7. springboot Properties加载顺序源码分析

    关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...

  8. (五)myBatis架构以及SQlSessionFactory,SqlSession,通过代理执行crud源码分析---待更

    MyBatis架构 首先MyBatis大致上可以分为四层: 1.接口层:这个比较容易理解,就是指MyBatis暴露给我们的各种方法,配置,可以理解为你import进来的各种类.,告诉用户你可以干什么 ...

  9. springMvc的执行流程(源码分析)

    1.在springMvc中负责处理请求的类为DispatcherServlet,这个类与我们传统的Servlet是一样的.我们来看看它的继承图 2. 我们发现DispatcherServlet也继承了 ...

随机推荐

  1. PIL对象和numpy三维数组的互相转换

    #https://stackoverflow.com/questions/384759/how-to-convert-a-pil-image-into-a-numpy-array from PIL i ...

  2. LGOJ1264 K-联赛

    这题其实不难想到 Description link 题意太长了,概括不来,去题库里扫一眼吧(但是很好懂) Solution \[Begin\] 考虑一个事情:每一个队伍的输局是没有用的 贪心一下,让每 ...

  3. 图论模型--dijstra算法和floyd算法

    matlab代码实现:https://blog.csdn.net/weixin_40108753/article/details/81237585 python代码实现:

  4. Codeforces 1288C - Two Arrays

    题目大意: 给定n和m,有两个数组,两个数组的长度都等于m 数组内每个元素都在1到n中 对于两个数组对应的位置i,必须满足a[i]<=b[i] a数组必须是不下降的序列 b数组必须是不上升的序列 ...

  5. Django专题-Cookie

      Cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响, ...

  6. Python模块——base64

    简介 base64模块是用来作base64编码解码,常用于小型数据的传输.编码后的数据是一个字符串,其包括a-z.A-Z.0-9./.+共64个字符,即可用6个字节表示,写出数值就是0-63.故三个字 ...

  7. linux中常见压缩文件格式

    文件后缀名 说明 *.zip zip 程序打包压缩的文件 *.rar rar 程序压缩的文件 *.7z 7zip 程序压缩的文件 *.tar tar 程序打包,未压缩的文件 *.gz gzip 程序( ...

  8. php面向对象理解(一)

    常用的继承过程,以及对public.private.protected修饰符的理解: /*****************************父类************************* ...

  9. cmd释放重新获取IP

    1.打开电脑的命令提示符运行设置窗口之后,我们收入  ipconfig/release  ,然后点击回车键  ,释放之前获取的IP地址 2.释放之前的IP地址之后,我们在输入  ipconfig/re ...

  10. Navicat for MySQL远程连接报10038的错误

    #################################################### """ 1.网络检测 1)ping主机可以: 2)telnet ...