1、spring拦截器介绍
spring针对处理器映射器提供了一种拦截器的机制,允许我们自定义一些处理逻辑,比如打印日志、校验用户是否登录等,然后spring会在调用最终的处理器前后执行我们自定义的逻辑。
注:
(1)处理器映射器,也叫HandlerMapping,关于它的作用,可以简单理解为它能根据请求路径找到我们在controller中写的能处理外部请求的方法。
(2)本文所展示的代码基于springboot 2.7.0版本(对应spring 5.3.20版本)
1.1、 拦截器的结构
spring要求我们自定义的拦截器需要实现HandlerInterceptor接口,我们先看下HandlerInterceptor接口内容:
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 {
}
}
简要解释下上面三个方法:
-
preHandle方法
在目标处理器(controller接口方法)运行之前执行,返回boolean值
-
postHandle方法
在目标处理器(controller接口方法)运行之后执行
-
afterCompletion方法
在整个请求完成(页面渲染)之后执行
1.2、拦截器原理
先为大家展示一下spring执行拦截器调用相关的代码,重点就在DispatcherServlet类中
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 第一步,找到请求对应的处理器,并生成处理器链,里面包括处理器信息以及拦截器列表信息
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 第二步,通过处理器链调用各个拦截器的preHandler方法,如果返回了false,说明不能执行目标处理器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 第三步,执行目标处理器方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 第四步,通过处理器链(倒序)调用各个拦截器的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 处理目标处理器返回的数据(进行页面渲染),并倒序调用各个拦截器的afterCompletion方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 出现异常时,执行本类中triggerAfterCompletion方法,进而调用mappedHandler.triggerAfterCompletion
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
if (mappedHandler != null) {
// 只要处理器链对象不为空,就直接去调用处理器链里面的拦截器列表中已执行拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, ex);
}
throw ex;
}
上面代码块中提到的mappedHandler的applyPreHandle、applyPostHandle和triggerAfterCompletion方法位于HandlerExecutionChain类,实现如下:
HandlerExecutionChain类
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
// 此下标记录了当前拦截器执行到了哪一个,方便倒序执行拦截器的afterCompletion方法
this.interceptorIndex = i;
}
return true;
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
// applyPreHandle方法记录了已经执行了preHandle方法的拦截器在拦截器列表中的下标,也即interceptorIndex的值
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
// 倒序执行所有拦截器的postHandle方法
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
简单总结下spring拦截器的原理:
-
DispatcherServlet根据当前的请求找到执行器链(内部封装了目标处理器,以及目标处理器拥有的拦截器列表)
-
首先会顺序执行拦截器链中的各个拦截器的preHandle方法,这时候会有两种情况
- 每个拦截器的preHandle都返回了true,所有处理器的preHandle都会得到执行,然后进入第3步
- 如果执行到某个拦截器的preHandle方法返回了false,就会倒序执行所有已经执行过的拦截器的afterCompletion方法,并且请求会直接结束,不会再执行后续的操作
-
所有的拦截器都返回了true,则执行目标方法(接口)
-
接着就是倒序执行所有拦截器的postHandle方法
-
然后就是渲染页面,并倒序执行所有拦截器的afterCompletion方法
-
当上面的第2步和第5步之间的任何一步出了异常,也都会倒序执行所有拦截器的afterCompletion方法
2、拦截器的使用
我们可以自定义一个拦截器,针对未登录用户的请求进行认证拦截。
2.1、定义拦截器
实现思路:
- 定义LoginInterceptor拦截器,实现HandlerInterceptor接口。
- 正常情况下,用户登录成功后,会将用户信息保存到session中,如果通过用户的请求,无法在session中查询到用户信息,说明用户未登录。
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
UserDTO user = (UserDTO) session.getAttribute("USER");
//session没有用户信息,说明用户未登录,阻止继续处理用户的请求,直接返回false
if(user == null) {
log.warn("用户未登录,不予处理请求");
return false;
}
return true;
}
}
UserDTO的信息如下:
public class UserDTO {
private String name;
private String userId;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2.2、注册拦截器到容器
定义WebMvcConfig配置类,实现WebMvcConfigurer接口
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}
结束语
本文先分享到这里,觉得有收获的朋友,可以关注我,或者进行分享或收藏,有疑惑的也可以来私聊评论,我会及时进行回复~