前文
在 [[SpringMvc是如何管理Controller]] 文章中,我们分析 DispatcherServlet 在分发请求时,根据 request 找 handler 是依靠 HandlerMapping,通过 handler 找到 handlerAdapter 的,是 handlerAdapter 自己的 support 方法,DispatcherServlet 忙了半天,就只是“指挥交通”了,所以,分析其初始化,继续套索 springmvc 的秘密,这次我们重点关注初始化流程。
先说结论:
initHandlerMappings 做的事很简单:
容器将 @Controller 类下 @RequestMapping 注解的方法,扫描初始为 HandlerMapping 实例,springmvc 的 onRefresh 调用 initHanderMappings 时,从容器中取出放到 DispatcherServlet 单例的成员变量 handlerMappings 中。
initHandlerAdapters 同理。都是容器初始化,已经按类分别初始化好了。spring 牛逼啊。
后面记录分析的过程,顺便了解下 dispatcherServlet#onRefresh
方法的执行流程。
找到入口
固定手法,SpringMvc 的初始化,可以先从 dispatcherServlet 的 onRefresh 方法入手, onRefresh->initStrategies,九大组件的初始化,这不就是我要的滑板鞋么。
意料之外
然后老规矩,直接将断点打在 initStrategies(context)
这行,然后 debug 启动,等待断点处高亮,这里先来了一个意料之外,应用顺利启动了,没进到断点处!!
难不成是在处理请求时触发?启用 postman 发起测试请求,果然,这次抓到请求了,DispatcherServlet 的初始化,就是在第一次请求中处理的。也就是使用了懒加载方式进行初始化。
根据堆栈,往上顺腾摸瓜,这样,我们就发现了几个 init 方法以及其所在的类。
调用顺序是:
org.apache.catalina.core.StandardWrapperValve#invoke
org.apache.catalina.core.StandardWrapper#allocate
org.apache.catalina.core.StandardWrapper#initServlet
javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
org.springframework.web.servlet.HttpServletBean#init
org.springframework.web.servlet.FrameworkServlet#initServletBean
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
org.springframework.web.servlet.DispatcherServlet#onRefresh
org.springframework.web.servlet.DispatcherServlet#initStrategies
既然是懒加载,那我们就可以找一找相关的逻辑,判断是否已经加载、初始化,allocate 就是你了,别看了别人了,点击去:
org.apache.catalina.core.StandardWrapper#allocate
@Override
public Servlet allocate() throws ServletException {
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
if (!singleThreadModel) {
// Load and initialize our instance if necessary
if (instance == null || !instanceInitialized) {
synchronized (this) {
if (instance == null) {
// 根据类目,new servlet实例
// instance = (Servlet) instanceManager.newInstance(servletClass);
instance = loadServlet();
}
//如果没初始化,就初始化
if (!instanceInitialized) {
//初始化实例
initServlet(instance);
}
}
}
//代码略...
}
private synchronized void initServlet(Servlet servlet)
throws ServletException {
//已经初始化了,就直接返回
if (instanceInitialized && !singleThreadModel) return;
// 执行servlet接口的init方法
try {
//是否启用安全开关
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",
servlet,
classType,
args);
success = true;
} finally {
//....
}
} else {
//facade参数:ServletConfig config
servlet.init(facade);
}
instanceInitialized = true;
}
}
在 org.apache.catalina.core.StandardWrapper#allocate
中,确实存在比较简单判断初始化逻辑,继续跟进 initServlet 方法,就可以找到 servlet.init(facade),入参 facade 乍一看不太明白,其实就是 ServletConfig 的实现类,写成这样 servlet.init(servletConfig) 就更明了。
现在,我们回到正轨,继续分析初始化之旅。
回到正轨
刚看到的 servlet.init(facade)方法,就是调用顺序中的第四步,javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
,点开就会发现,这个是接口,实现有很多:
源码中,一般都是面向接口编程的,这十几个实现类,就是不同的 servlet 的实现方法,我们没有精力也没有必要全部搞清楚,继续聚焦到 DispatcherServlet 类,现在就通过 idea,查看一把 DispatcherServlet 类图:
可以发现 HttpServletBean,就是我们的目标。现在我们就可以重点关注这三个类:HttpServletBean、FrameworkServelt、DispatcherServlet。
渐入佳境
现在,我们开始对这是三个类做一番了解:
org.springframework.web.servlet.HttpServletBean#init
org.springframework.web.servlet.FrameworkServlet#initServletBean
org.springframework.web.servlet.DispatcherServlet#onRefresh
对这个三个方法的分析,这里重点"江南一点雨"的这篇文章,写的很透彻清晰: mp.weixin.qq.com/s/JImMPTGzH…
org.springframework.web.servlet.HttpServletBean#init
源码:
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
*
* 文档说的很明白:将此servlet的配置参数映射到bean属性,并调用子类初始化。
*/
@Override
public final void init() throws ServletException {
// 1.将配置信息,设置到bean的属性中,也就是获取配置文件信息
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//读取配置参数,并设置到当前bean的属性中
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//初始化并设值
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
throw ex;
}
}
// 2. 调用子类初始化。
initServletBean();
}
/**
*Subclasses may override this to perform custom initialization. All bean properties of this servlet will have been set before this method is invoked.
翻译:让子类自定义初始化逻辑。所有的servlet配置参数已设置好了(赋值到了当前bean的属性中)。
*/
protected void initServletBean() throws ServletException {
}
HttpServletBean 的 init 初始化,就做了两件事
org.springframework.web.servlet.FrameworkServlet#initServletBean
源码:
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
try {
//初始化webApplicationContext
this.webApplicationContext = initWebApplicationContext();
//初始化servlet
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
throw ex;
}
}
FrameworkServlet 的 initServletBean 初始化方法,也只做了两件事:
但第二个方法点进去,只是个空实现,在 DispatcherServlet 中,也没有重写,也就是实际未运行任务代码。我们的重点就在 initWebApplicationContext 方法中,DispatcherServlet#onRefresh
也就在是在这个方法中被调用的。
/**
* Initialize and publish the WebApplicationContext for this servlet.
*/
protected WebApplicationContext initWebApplicationContext() {
//获取spring容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// find
wac = findWebApplicationContext();
}
if (wac == null) {
// new
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//调用子类的onRefresh方法
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
return wac;
}
这段初始化 WebApplicationContext,可以理解为初始化父子容器,然后调用子类 DispatcherServlet#onRefresh
方法。
这里有几个疑问点:
org.springframework.web.servlet.DispatcherServlet#onRefresh
源码:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* 大名鼎鼎的九大组件初始化
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
初始化方法
initHandlerMappings
org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
的源码如下:
/**
* Initialize the HandlerMappings used by this class.
* If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 在 ApplicationContext 容器中,找到所有类型为 HandlerMappings 的实例, 也包括在父类容器中
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// 集合不为空,按优先级排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// 此异常被忽略,由后续兜底逻辑处理
}
}
// 通过以上步骤还没有拿到handlerMappings的值,就用默认策略,确保不为空
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
这段代码主要内容是,从当前容器、或者父容器中,根据 detectAllHandlerMappings 状态,是按类型,或者按名字获取 handlerMappings 实例。
按类型获取:
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
按名字获取:
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
我们知道,在 springmvc 中,HandlerMapping 就是 controller 下的 method,而本文的目的也就是想知道,controller 的 method 是何时变身为 HandlerMapping 的,看到源码却让我们一拳打到了棉花上,答案如此简单:从容器中找到的...
看 HandlerMapping 的接口的实现类:
我们在重点关注 RequestMappingInfoHandlerMapping、RequestMappingHandlerMapping,三者的关系:
其中 RequestMappingInfoHandlerMapping 是抽象类,其唯一的实现类 RequestMappingHandlerMapping。
而仔细看 RequestMappingHandlerMapping 的命名,RequestMapping+HandlerMapping,和我们天天写的 RequestMapping 注解,是不是就对上了。
继续翻阅源码注释:
/**
* Abstract base class for classes for which {@link RequestMappingInfo} defines
* the mapping between a request and a handler method.
*/
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping {
该基类的作用:
用于{@link RequestMappingInfo}定义请求和处理程序方法之间映射的类的抽象基类。
/**
* Creates {@link RequestMappingInfo} instances from type and method-level
* {@link RequestMapping @RequestMapping} annotations in
* {@link Controller @Controller} classes.
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {
该实现类的作用:
将在 @Controller 修饰的类下标有 @RequestMapping 注解的方法创建为 RequestMappingInfo 类的实例
现在流程很清晰了,就是容器将 @Controller 类下 @RequestMapping 注解的方法,扫描初始为 HandlerMapping 实例,springmvc 的 onRefresh 调用 initHanderMappings 时,从容器中取出放到 DispatcherServlet 单例的成员变量 handlerMappings 中。
以为这篇文章可以将初始化收尾,看来还需要去 spring 的初始化流程中转一圈才能真正看到真正的初始化。
initHandlerAdapters
org.springframework.web.servlet.DispatcherServlet#initHandlerAdapters
的源码如下:
/**
* Initialize the HandlerAdapters used by this class.
* If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
* we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
}
}
仔细看完发现,和 initHandlerMappings 一模一样的套路,容器真好啊,已经按需准备好了各种 bean,任你随便使用,牛皮!!
虽流程一致,但我们还是看一眼,混个眼熟。
HandlerAdapter 接口的实现类如下:
重点看下 RequestMappingHandlerAdapter 类,源码如下:
/**
* Extension of {@link AbstractHandlerMethodAdapter} that supports
* {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.
*
* Support for custom argument and return value types can be added via
* {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers},
* or alternatively, to re-configure all argument and return value types,
* use {@link #setArgumentResolvers} and {@link #setReturnValueHandlers}.
*
*/
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
此方法文档说明的意思是:
扩展 AbstractHandlerMethodAdapter,支持使用@RequestMapping 注解的 HandlerMethods。
可以通过 setCustomArgumentResolvers 和 setCustomReturnValueHandlers 来添加自定义参数和返回值类型,或者可以使用 setArgumentResolvers 和 setReturnValueHandlers 重新配置所有参数和返回值类型。
HandlerAdapter 适配器主要做了增强,可以添加很多自定义的 Resolver 在执行 handler 前、后执行。
心得
从上篇文档 [[SpringMvc是如何管理Controller]] 中就有个感觉,spring 中,职责清晰,各司其职,DispatcherServlet 主要做编排,借助了 HandlerMapping 给每个请求分配到对应的 handler 上。
本篇文章完成后发现,initHandlerMapping 的初始化,就是从容器中获取对应类型的组件(bean)而已,如此简单,如此高效。
但还有些需要深挖的点,并且我们也常用的:
例如在 HandlerAdapter 中,可以设置自定义的 Resolvers,这是怎么完成、并工作的。
父子容器的关系本篇没介绍的足够清晰。
后面先对 spring 的初始化动手,不积跬步无以至千里。
参考
# SpringMVC 初始化流程分析
mp.weixin.qq.com/s/JImMPTGzH…
www.springcloud.io/post/2022-0…
www.baiyp.ren/SpringMVC-0…