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. 基于Struts2开发公司职工管理系统 Java源码

    开发环境: Windows操作系统 开发工具: MyEclipse+Jdk+Tomcat+MySql数据库 职工管理系统作为一种管理软件正在各公司中得到越来越广泛的应用,且已达到了良好效果. 运行效果 ...

  2. The flower(寻找出现m次以上,长度为k的子串)

    链接:https://ac.nowcoder.com/acm/contest/3665/B来源:牛客网 题目描述 Every problem maker has a flower in their h ...

  3. 计蒜客 置换的玩笑(DFS)

    传送门 题目大意: 小蒜头又调皮了.这一次,姐姐的实验报告惨遭毒手. 姐姐的实验报告上原本记录着从 1 到 n 的序列,任意两个数字间用空格间隔.但是“坑姐”的蒜头居然把数字间的空格都给删掉了,整个数 ...

  4. python thrift demo

    简介Thrift最初由Facebook研发,主要用于各个服务之间的RPC通信,支持跨语言,常用的语言比如C++, Java, Python, PHP, Ruby, Erlang, Perl, Hask ...

  5. 吴裕雄--天生自然 PHP开发学习:多维数组

    <pre> <?php // 二维数组: $cars = array ( array("Volvo",100,96), array("BMW" ...

  6. [代码审计]PCWAP

    为什么想要审计这套源码呐?之前看到某大佬在做反钓鱼网站的时候,发现钓鱼网站的后台用的就是PCWAP,所以我觉得有必要审计一下,顺便记录,打击网络犯罪! 0x00 PCAWAP: PCWAP手机网站建站 ...

  7. PAT Advanced 1096 Consecutive Factors (20) [数学问题-因子分解 逻辑题]

    题目 Among all the factors of a positive integer N, there may exist several consecutive numbers. For e ...

  8. 给select赋值之后,再点击选择下拉值时,显示一值不变的解决

    在一个项目需求中,请求数据,得到的数据dataAll渲染到页面的select下拉表单中,当时是需要一进页面就要默认选中第一个选项,所以直接将dataAll的第一个索引值赋值给了表单的绑定值formVa ...

  9. mysql按月分表, 组合查询

    每个月月底最后一天建好下个月的空表 或每年底建1到12月的空表 , table_201901,table_201902,table_201903 增加记录不需要修改,insert到当月对应表就好了. ...

  10. 云服务器上安装MSF环境

    在校园网内,禁止使用路由器之类的,所以就导致自己本地虚拟机环境出现各种各样的网络问题,解决起来过于繁琐,为此在服务器上安装MSF 0x00 安装MSF 使用Xshell等工具连上远程服务器后,首先确保 ...