1、本文内容
- 回顾下springmvc处理请求的过程(流程图)
- 如何干预springmvc的处理流程?
- 加入拦截器后springmvc的处理过程
- 拦截器的用法(具体2个步骤)
- 多个拦截器的执行顺序
- 通过案例验证拦截器的执行顺序
- 一起读源码
- 领取后端必备的200本书籍
2、回顾下springmvc处理请求的过程
简化下过程,如下图,过程还是非常简单的
3、如何干预springmvc的处理流程?
比如我们的系统中,除了登录的方法,其他所有方法都需要先验证一下用户是否登录了,若未登录,让用户先跳转到登录页面,最笨的方法是在所有需要验证的方法内部都加上验证的代码,那么有没有更好的方法呢?
如下图,如果我们将验证登录的代码放在调用自定义controller的方法
之前,是不是就特别爽了,就不用在所有代码中都添加验证代码了:
springmvc确实为我们考虑到了这种需求,springmvc在处理流程中为我们提供了3个扩展点可以对整个处理流程进行干预,这个就是springmvc中拦截器提供的功能,下面咱们来看一下拦截器怎么玩的。
4、拦截器(HandlerInterceptor)
springmvc中使用org.springframework.web.servlet.HandlerInterceptor
接口来表示拦截器,如下,提供了3个默认方法。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
下面来解释下这3个方法。
preHandle方法
在调用自定义的controller之前会调用这个方法,若返回false,将跳过controller方法的调用,否则将进入到controller的方法中。
postHandle方法
调用自定义controller中的方法之后会调用这个方法,此时还没有渲染视图,也就是还没有将结果输出到客户端
afterCompletion方法
整个请求处理完毕之后,即结果以及输出到客户端之后,调用这个方法,此时可以做一些清理的工作,注意这个方法最后一个参数是Exception类型的,说明这个方法不管整个过程是否有异常,这个方法都会被调用。
其他说明
- 3个方法中的hander参数表示处理器,通常就是我们自定义的controller
5、加入拦截器后springmvc的处理流程
加入拦截器之后处理流程如下图,注意黄色背景的几个对应拦截器的3个方法。
6、拦截器的用法(2步骤)
step1:定义拦截器
自定义一个类,需要实现
org.springframework.web.servlet.HandlerInterceptor
接口,如下,然后实现具体的方法
public class HandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
step2:将自定义的拦截器添加到springmvc配置文件中
配置如下,需要将自定义的拦截器添加到springmvc配置文件中
- 可以同时配置多个拦截器,每个拦截器通过mvc:interceptor标签来定义,多个拦截器之间可以指定顺序,顺序和mvc:interceptor定义的顺序一致
- 每个拦截器需要指定实现类、拦截的url、排除的url
<!-- interceptors用来定义拦截器,其内部可以定义多个拦截器 -->
<mvc:interceptors>
<!-- mvc:interceptor 标签用来定义一个拦截器 -->
<mvc:interceptor>
<!-- 用来指定拦截器匹配的url,比如/user/**会拦截所有以/user开头的url -->
<mvc:mapping path="/user/**"/>
<!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用来指定拦截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
</mvc:interceptor>
<!-- 其他拦截器配置信息 -->
<mvc:interceptor>
.....
</mvc:interceptor>
</mvc:interceptors>
7、多个拦截器时如何执行?
当请求的url匹配到多个拦截器的时候,执行顺序如下图
- preHandler方法是顺序执行的,即和定义的顺序是一致的
- 而拦截器中的其他2个方法postHandler、afterCompletion是逆序执行的,和pewHandler的顺序相反
8、案例验证拦截器的执行顺序
下面通过案例结合3个场景来看一下拦截器的执行顺序,加深大家的理解。
准备代码
UserController
package com.javacode2018.springmvc.chat09.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login() {
return "login view";
}
@RequestMapping("/add")
public String add() {
return "add view";
}
@RequestMapping("/del")
public String modify() {
return "modify view";
}
@RequestMapping("/list")
public String list() {
return "list view";
}
}
创建2个拦截器
拦截器1:HandlerInterceptor1
package com.javacode2018.springmvc.chat09.intercetor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
拦截器2:HandlerInterceptor2
package com.javacode2018.springmvc.chat09.intercetor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
}
}
配置文件中配置拦截器
下面将2个拦截器加入springmvc的配置文件中,都会拦截/user开头的所有请求,/user/login被排除在外
<!-- interceptors用来定义拦截器,其内部可以定义多个拦截器 -->
<mvc:interceptors>
<!-- mvc:interceptor 标签用来定义一个拦截器 -->
<mvc:interceptor>
<!-- 用来指定拦截器匹配的url -->
<mvc:mapping path="/user/**"/>
<!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用来指定拦截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
</mvc:interceptor>
<!-- mvc:interceptor 标签用来定义一个拦截器 -->
<mvc:interceptor>
<!-- 用来指定拦截器匹配的url -->
<mvc:mapping path="/user/**"/>
<!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
<mvc:exclude-mapping path="/user/login"/>
<!-- 用来指定拦截器 -->
<bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
场景1
按照下列表格,调整下2个拦截器的preHandle方法返回值
拦截器 | preHandle方法返回值 |
---|---|
HandlerInterceptor1 | true |
HandlerInterceptor2 | true |
访问http://localhost:8080/chat09/user/add
,输出
HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor2.postHandle
HandlerInterceptor1.postHandle
HandlerInterceptor2.afterCompletion
HandlerInterceptor1.afterCompletion
场景2
按照下列表格,调整下2个拦截器的preHandle方法返回值
拦截器 | preHandle方法返回值 |
---|---|
HandlerInterceptor1 | false |
HandlerInterceptor2 | true |
访问http://localhost:8080/chat09/user/add
,输出
HandlerInterceptor1.preHandle
场景3
按照下列表格,调整下2个拦截器的preHandle方法返回值
拦截器 | preHandle方法返回值 |
---|---|
HandlerInterceptor1 | true |
HandlerInterceptor2 | false |
访问http://localhost:8080/chat09/user/add
,输出
HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor1.afterCompletion
9、源码解析
拦截器的执行过程主要位于下面这段代码中
代码位置:org.springframework.web.servlet.DispatcherServlet#doDispatch
如下代码,咱们将关键代码提取出来,大家注意看注释,解释了每个方法内部干的事情,具体每个方法的内部,咱们就不进去了,很简单,有兴趣的可以自己去看,这里给大家提点建议:看源码的时候,先站在高的层次上面看代码,了解大的功能及流程之后,再去细看某个功能点,要避免上来就陷入细节中,容易迷失方向。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
Exception dispatchException = null;
try {
//①、根据请求找到处理器
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//②、内部会调用拦截器的preHandler方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//③、调用实际的处理器(即这里面会调用咱们controller中的方法)
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//④、调用拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
//⑤、渲染视图 & 调用拦截器的afterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
//⑥:异常情况,调用拦截器的afterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
}
10、案例代码
git地址:https://gitee.com/javacode2018/springmvc-series
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。