本系列文章皆在分析SpringMVC
的核心组件和工作原理,让你从SpringMVC
浩如烟海的代码中跳出来,以一种全局的视角来重新审视SpringMVC
的工作原理.
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
在上一章SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器 中,我们通过对doDispatch
方法中getHandler
方法分析由浅入深的讨论了HandlerMapping
组件的相关内容。通过分析我们知道了HanlderMapping
的主要工作就是根据前端传来的请求,然后找到合适的处理器。
此外,在分析HanlderMapping
的过程中我们注意到在getHandler
方法的内部会遍历全部的HanlderMapping
信息,然后逐一进行匹配。如果找到合适的处理器组件,则返回一个HandlerExecutionChain
对象。上一节中我们指出HandlerExecutionChain
其实相当于对处理器的一种封装,其内部会包含处理器handler
和拦截器HandlerInterceptor
。其中处理器很好理解,无非就是在程序中被我们使用@GetMapping,@PostMapping
注解所标记的方法,那HandlerInterceptor
又是什么呢?别急,本章我们便来揭开HandlerInterceptor
神秘面纱。
在doDispatch
的调用链中,有关HandlerInterceptor
的调用位于方法mappedHandler.applyPreHandle
中。所以要了解HandlerInterceptor
在处理Http
请求中的作用,我们首先应该关注mappedHandler.applyPreHandle
的逻辑。(mapperHandler.applyPostHandler
与之类似)
(注:为了行文整体的连贯性,本文将首先介绍HandlerInterceptor
的相关调用信息,即方法mappedHandler.applyPreHandle
背后的调用关系链;方法getHandlerAdapter
的背后的逻辑将在后续介绍。)
下图展示了本系列文章重点分析的组件信息,其中 HandlerInterceptor
为本文分析的重点。
applyPostHandle背后的逻辑
HandlerExecutionChain # applyPreHandle和applyPostHandle的方法信息
java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// <1> 获得拦截器数组
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// <2> 遍历拦截器数组
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// <3> 前置处理
if (!interceptor.preHandle(request, response, this.handler)) {
// <3.1> 已完成处理 拦截器
triggerAfterCompletion(request, response, null);
// 返回 false ,前置处理失败
return false;
}
// <3.2> 标记 interceptorIndex 位置
this.interceptorIndex = i;
}
}
// <4> 返回 true ,前置处理成功
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果配置有拦截器
if (!ObjectUtils.isEmpty(interceptors)) {
// 逆序遍历拦截器信息
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
// 执行后置处理器
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
调用链中applyPreHandle
和applyPostHandle
方法的逻辑如上所示。此时,我们以为applyPreHandle
例来进行分析有关HandlerInterceptor
的相关处理逻辑,applyPreHandle
的大致逻辑如下:
- 获得拦截器数组,通过上面的
getInterceptors()
方法,获得interceptors
数组 - 遍历
interceptors
拦截器数组 - 依次执行拦截器的前置处理
- 返回
true
,拦截器们的前置处理都成功
(注:拦截器在执行过程中,如如果有某个拦截器的前置处理失败,则调用 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,触发拦截器们的已完成处理,最后返回 false
)
可以看到,applyPreHandle
和applyPostHandle
在逻辑上大致相似,基本都是遍历持有的HanderInterceptor
然后执行其中所定义的applyPreHandle
和applyPostHandle
方法。
如何实现对一个请求的前置、后置处理
在分析HanderInterceptor
之前,不妨先思考一个问题,对于一个请求,哪些方式可以对其进行一些前置和后置处理操作?可能你会想到如下几种实现方式:
- 切面编程:
Aop切面编程
- 过滤器:
Filter
接下来,我们便逐一分析上述方案的优劣。对于AOP
切面编程技术而言,其可以实现对一个请求进行前置操作,以及后置操作和环绕操作等。 但同时会引入切面和动态代理等额外的概念,频繁的使用Aop
相关技术,会使得业务代码的逻辑变得复杂难以理解。
对于过滤器Filter
来说,其同Servlet
、Listener
并称为java Web
的三大核心组件,其可在doFilter
方法中定义预处理和后处理逻辑,并在方法中通过filterChain.doFilter
继续向下传递,从而形成拦截器链。
但Filter
是在请求进入Servlet
容器之前进行预处理,以及响应离开容器之前进行后处理。 所以某种意义上来讲Filter
是工作在Servlet
容器级别的,它对所有进入容器的请求和离开容器的响应进行处理,所以其在处理请求上具有更粗的粒度。
而SpringMVC
中的HanderInterceptor
(拦截器)的出现恰好可以解决上述方案中存在的问题。接下来,我们便看看HandlerInterceptor
是如何来对一个请求进行拦截的。
深入理解HandlerInterceptor
在SpringMVC
中,HandlerInterceptor
)是一种拦截器组件,用于在请求的处理器方法执行前后进行拦截和处理 。HandlerInterceptor
可以用于实现一些公共的处理逻辑,例如身份验证、日志记录、性能监控等。
HandlerInterceptor的方法信息
java
public interface HandlerInterceptor {
/**
* 前置处理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 执行之前
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* 后置处理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 执行成功之后
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 完成处理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 执行之后(无论成功还是失败)
* 条件:执行 {@link #preHandle(HttpServletRequest, HttpServletResponse, Object)} 成功的拦截器才会执行该方法
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
HandlerInterceptor
组件在方法定义层面非常简洁,主要定义了前置和后置处理两个方法。其中前置处理方法preHandle
多用于判断该拦截器所能拦截的信息;而后置方法postHandle
则主要用于对请求完成后的处理操作。
(注:有关afterCompletion
的调用可查看doDispatcher
中triggerAfterCompletion
方法。)
拦截器的初始化
正如上一章SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器所述,在AbstractHandlerMapping
会持有一个 initInterceptors
的数组信息,从而用来记录容器中全部的拦截器信息,而其又实现 ApplicationContextAware
接口,所以interceptor
的初始化有可能是在AbstractHandlerMapping
中完成的。为了验证我们这一猜测,我们不妨点开AbstractHandlerMapping
中的initApplicationContext
方法进行查看,相关代码如下所示:
AbstractHandlerMapping
java
@Override
protected void initApplicationContext() throws BeansException {
// <1> 空实现,交给子类实现,用于注册自定义的拦截器到 interceptors 中
// 目前暂无子类实现
extendInterceptors(this.interceptors);
// <2> 扫描已注册的 MappedInterceptor 的 Bean 们,添加到 mappedInterceptors 中
detectMappedInterceptors(this.adaptedInterceptors);
// <3> 将 interceptors 初始化成 HandlerInterceptor 类型,添加到 mappedInterceptors 中
// 此处主要处理(1) 处自定义的,全局配置的在第二步就应该全部加载
initInterceptors();
}
protected void initInterceptors() {
// 遍历 List<Object> interceptors
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
// 将 interceptors 初始化成 HandlerInterceptor 类型
// 添加到 mappedInterceptors 中
// 注意,HandlerInterceptor 无需进行路径匹配,直接拦截全部
// List<HandlerInterceptor> adaptedInterceptors类型,添加拦截器信息
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
// .....省略其他无关代码
}
通过上述代码我们知道,在HandlerMapping
进行加载时,会加载容器中全部的拦截器,并将相关信息保存在AbstractHandlerMapping
的成员变量adaptedInterceptors
中。其在容器中初始化逻辑如下所示:
具体而言,在Spring
扫描类信息的时候会加载并执行全部的BeanPostProcessor
信息,而在众多的BeanPostProcessor
中,有一个ApplyBeanPostProcessor
专门用于处理Aware
接口的相关扩展。由于AbstractHandlerMapping
实现了ApplicationContextAware
接口,所以其在加载过程中可以完成某些扩展操作。
接下来,我们便将继续分析如何将这些拦截器应用到请求的处理当中。但在分析之前,我们需要先对HandlerExecutionChain
有所介绍,这样有助于我加深我们的对于处理流程的理解。
走进HandlerExecutionChain
在开始分析HandlerExecutionChain
之前,我们不妨考虑这样一个问题。在SpringMVC
中通常我们的处理器通常可以看做的被@GetMapping、@PostMapping
等所标记的一个方法,而处理的执行本质就是方法执行。换言之,如果要对一个请求进行前置和后置处理,我们只需在方法执行前后进行可以拦截该方法拦截器信息即可。 具体逻辑如下所示:
此时的这个逻辑其实同AOP
编程中的前置处理器和后置处理器的思想很类似。此时我们的问题就是能否通过一种非动态代理的方式来进行实现这样的增强逻辑呢?问题的答案就在HandlerExecutionChain
中,
这其实就是HandlerE
对其相关代码如下:
java
public class HandlerExecutionChain {
/**
* 处理器
*/
private final Object handler;
/**
* 拦截器数组
*/
@Nullable
private HandlerInterceptor[] interceptors;
/**
* 拦截器数组。
*
* 在实际使用时,会调用 {@link #getInterceptors()} 方法,初始化到 {@link #interceptors} 中
*/
@Nullable
private List<HandlerInterceptor> interceptorList;
/**
* 已成功执行 {@link HandlerInterceptor#preHandle(HttpServletRequest, HttpServletResponse, Object)} 的位置
*
* 在 {@link #applyPostHandle} 和 {@link #triggerAfterCompletion} 方法中需要用到,用于倒序执行拦截器的方法
*/
private int interceptorIndex = -1;
public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[]) null);
}
//.....省略其他无关代码
}
可以看到HandlerExecutionChain
是对处理器(handler
)和拦截器(interceptors
)的封装。其关键成员如下所示:
handler
:请求对应的处理器对象,可以先理解为HandlerMethod
对象(例如我们常用的@RequestMapping
注解对应的方法会解析成该对象),也就是我们的某个 Method 的所有信息,可以被执行interceptors
:请求匹配的拦截器数组interceptorIndex
:记录已成功执行前置处理的拦截器位置,因为已完成处理只会执行前置处理成功的拦截器,且倒序执行
(注:HandlerExecutionChain
中的applyPostHandle
和applyPreHandle
在前文已经进行了详细的描述)
在上一章SpringMVC流程分析(四):SpringMVC中如何为一个请求选择合适的处理器的分析中,我们知道HandlerMapping
的getHandler
方法会为一个请求找到一个合适的处理器
,并且其还会将可以拦截该请求的拦截器一并找出,进而将其全部封装到对象HandlerExecutionChain
中。这便是HandlerExecutionChain
中会将处理器和拦截器信息作为成员变量的原因。
事实上,通过HandlerMapping
的getHandler
返回的HandlerExecutionChain
对象中会持有可以适配该处理器的全部拦截器,此时只要定义确保,拦截器的前置方法在处理处理逻辑之前执行,同时保证拦截器的后置方法在处理处理逻辑之后执行就能完成对一个请求的前置和后置处理。
(注:为处理器寻找可以处理该请求的拦截器的相关逻辑可参考AbstractHandlerMapping
中的 getHandlerExecutionChain
方法的处理逻辑,限于篇幅原因本文不在此进行过多赘述~)
此时再回头看 DispatcherServlet # doDispatch
方法中有关处理器的执行逻辑是不是有种豁然开朗的感觉?
DispatcherServlet # doDispatch
java
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
. // 根据请求获取HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
// 对处理器进行适配(暂时忽略,下一章会讲适配器相关的内容)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行拦截器前置方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行处理逻辑
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行拦截器后置方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
通过上述代码不难发现,在SpringMVC
内部对一个请求进行前置和后置的处理并没有通过代理的方式来实现,而是通过一种类似静态代理的形式进行了实现。 此外,通过将处理器和增强信息封装到一个类中,接着在抽象层次更高的类中定义相关的执行顺序,从而实现逻辑的增强。这某种意义上来说其实又夹杂了模板方法的思想。
实际上,阅读源码的意义远不止于理解每句代码的含义,更重要的是通过阅读源码来体会代码的设计思想和类功能的拆分。为了理解这些内容,我们需要找到一条主线,这条主线可以是我们使用框架时的相关配置,也可以是某种既定的模板代码。以使用mybatis
为例,通常我们会构建sqlSession
工厂、获取sqlSession
,构建Mapper
代理对象等。沿着这些主线不断深入,我们才能对源码有更深入的认识和见解,才不至于在茫茫多的源码中迷失自我。
总结
本文以doDispahtch
方法中调用的applyPreHandle
为切入点,由浅入深的分析了HandlerInterceptor
的组件的功能、类结构关系等内容。研究了SpringMVC
拦截器的初始化时机,以及调用时机等。最后,我们对上一章提及的HandlerExecutionChain
对象进行了详细的介绍。