SpringMvc初始化之initHandlerMappings、initHandlerAdapters
前文
在 [[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…