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
更多免费学习资料可关注:itheimaGZ获取
来源:https://www.cnblogs.com/zhuxiaopijingjing/p/12289885.html