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. share团队冲刺2

    团队冲刺第二天 昨天:在网上学习app开发的简单操作代码,实现了简单的输出界面,学会了添加按钮控件. 今天:继续昨天的进度,先进行登陆界面窗口的制作. 问题:目前只能在activity添加简单代码,复 ...

  2. java基础-泛型的优点

    1.性能 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作.装箱和拆箱的操作很容易实现,但是性能损失较大.假如使用泛型,就可以避免装箱和拆箱操作. ...

  3. 图形化编程娱乐于教,Kittenblock实例,图章效果的音乐画面

    跟很多学生聊过,很多学生不是不努力,只是找不到感觉.有一点不可否认,同样在一个教室上课,同样是一个老师讲授,学习效果迥然不同.关键的问题在于,带入感,我能给出的建议,就是咬咬牙,坚持住,没有学不会的知 ...

  4. 吴裕雄--天生自然 PHP开发学习:函数

    <?php function writeName() { echo "Kai Jim Refsnes"; } echo "My name is "; wr ...

  5. tensorflow中使用指定的GPU及GPU显存

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本文目录 1 终端执行程序时设置使用的GPU 2 python代码中设置使用的GPU 3 设置tensorflow使用的显 ...

  6. 缩写: i = i + 1 和 i += 1,可以看做是 i 自加的值 是1。

    i +=  1; 这样有助于记忆: i自加的值等于1

  7. Sqlite教程(2) Data Access Object

    因为这个项目的业务层很薄,因此想在架构上尽量保持着「轻」,不会把创建DbHelper的interface. 而是直接用DAO创建DbHelper对象. DAO和DbHelper也是同样使用懒汉模式. ...

  8. 十六、linux系统网络基础

    1.网络是由IP构成的:network + host,以至于我们使用网络向外发信息,不会发错. 2.子网掩码这里要知道两点: 1)子网掩码不可能出现交叉部分,换句话说不会出现01010101的交叉现象 ...

  9. RequestContextHolder getHttpServletRequest

    package me.zhengjie.common.utils; import org.springframework.web.context.request.RequestContextHolde ...

  10. 吴裕雄--天生自然python学习笔记:python 用pyInstaller模块打包文件

    要想在没有安装 Python 集成环境的电脑上运行开发的 Python 程序,必须把 Python 文件打包成 .exe 格式的可执行 文件. Python 的打包工作 PyInstaller 提供了 ...