首页技术文章正文

springmvc拦截器及源码分析【技术点干货】

更新时间:2021-03-26 来源:黑马程序员 浏览量:

1577370495235_学IT就到黑马程序员.gif


springmvc拦截器是我们项目开发中用到的一个功能,常常用于对Handler进行预处理和后处理。本案例来演示一个较简单的springmvc拦截器的使用,并通过分析源码来探究拦截器的执行顺序是如何控制的。

1、springmvc拦截器使用


1.1 项目初始搭建


1.1.1 创建一个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,所以需要创建一个springmvc.xml文件(文件参考源码附件)放到classpath下

<!-- 前端控制器(加载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>

1.2 拦截器开发

1.2.1 准备两个拦截器

两个拦截器分别命名为MyInterceptor1MyInterceptor2

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 执行======");
    }
}
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 执行======");
    }
}


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>

这两个拦截器拦截规则相同,并且配置顺序拦截器1拦截器2之前!


1.3 测试拦截器效果

1.3.1 准备测试Controller

@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";
    }
}

该controller会转发到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 执行======
==2-1====前置拦截器2 执行======
>>>>>业务代码执行-查询用户ID为:1
==2-2=====后置拦截器2 执行======
==1-2=====后置拦截器1 执行======
>>>>>jsp页面的输出为:宙斯
==2-3======最终拦截器2 执行======
==1-3======最终拦截器1 执行======

通过打印日志发现,拦截器执行顺序是:

拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终

 


2、源码分析

经过测试发现拦截器执行顺序如下:

拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终

我们通过分析源码来探究下拦截器是如何执行的

2.1 DispatcherServlet

当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServletdoDispatch方法,我们将其取出并观察其核心代码(省略非关键代码)

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 {
        //...
    }
}

代码中有关拦截器执行的位置我都添加了注释,其中注释中标识的步骤中,3、5、6步骤是拦截器的关键步骤

 

其中,第一步中"获取执行链",执行链内容可以通过debug调试查看内容:

1616739961365_001.png

可以看到我们自定义的两个拦截器按顺序保存

2.2 拦截器步骤解析

doDispatch方法中,我们添加的注释的第【3】、【5】、【6】步骤是对拦截器的执行处理,现在分别来查看第【3】、【5】、【6】步骤执行的具体方法的源码

2.2.1 第【3】步骤

//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(前置处理)方法是按拦截器(拦截器1、拦截器2)顺序执行的,然后我们再来看步骤【5】

2.2.2 第【5】步骤

//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);
        }
    }
}

会发现,后置处理是按照拦截器顺序倒叙处理的!

我们最后来看下最终拦截器

2.2.3 第【6】步骤

//执行的方法
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方法:

//6、执行拦截器的最终方法
    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);
                }
            }
        }
    }

由此可以看到,拦截器的最终方法的执行也是按照倒叙来执行的,而且是在响应之后。

 

3、总结

拦截器常用于初始化资源,权限监控,会话设置,资源清理等的功能设置,就需要我们对它的执行顺序完全掌握,我们通过源码可以看到,拦截器类似于对我们业务方法的环绕通知效果,并且是通过循环收集好的拦截器集合来控制每个拦截器方法的执行顺序,进而可以真正做到深入掌握拦截器的执行机制!



猜你喜欢:

BUG是什么意思?只有“漏洞”这一个意思吗?

ASCII码对照表【黑马程序员】

依赖注入是什么?依赖注入介绍

Spring Cloud Hystrix原理详细介绍

黑马程序员高级java软件开发工程师课程

分享到:
在线咨询 我要报名
和我们在线交谈!