springmvc拦截器入门及其执行顺序源码分析

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

猜你喜欢

转载自www.cnblogs.com/zhuxiaopijingjing/p/12289885.html