SpringMvc初始化之initHandlerMappings、initHandlerAdapters

2023年 9月 21日 104.3k 0

前文

在 [[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,九大组件的初始化,这不就是我要的滑板鞋么。

image.png

意料之外

然后老规矩,直接将断点打在 initStrategies(context) 这行,然后 debug 启动,等待断点处高亮,这里先来了一个意料之外,应用顺利启动了,没进到断点处!!

难不成是在处理请求时触发?启用 postman 发起测试请求,果然,这次抓到请求了,DispatcherServlet 的初始化,就是在第一次请求中处理的。也就是使用了懒加载方式进行初始化。

image.png

根据堆栈,往上顺腾摸瓜,这样,我们就发现了几个 init 方法以及其所在的类。

image.png

调用顺序是:

  • 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),点开就会发现,这个是接口,实现有很多:
    image.png

    源码中,一般都是面向接口编程的,这十几个实现类,就是不同的 servlet 的实现方法,我们没有精力也没有必要全部搞清楚,继续聚焦到 DispatcherServlet 类,现在就通过 idea,查看一把 DispatcherServlet 类图:

    image.png

    可以发现 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 初始化,就做了两件事

  • 读取配置,并将配置参数值,设置到当前 bean 的属性中
  • 调用子类初始化
  • 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 初始化方法,也只做了两件事:

  • 初始化 webApplicationContext
  • 初始化 servlet。但空实现,也就是让子类重写
  • 但第二个方法点进去,只是个空实现,在 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 方法。
    这里有几个疑问点:

  • rootContext 是谁,父容器?如果是,是什么内容。
  • createWebApplicationContext 创建的是什么容器,字母意思是 servlet 容器,需要进一步了解。
  • servlet 容器(mvc)和 spring 容器的关系。
  • 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 的接口的实现类:
    image.png

    我们在重点关注 RequestMappingInfoHandlerMapping、RequestMappingHandlerMapping,三者的关系:
    image.png

    其中 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 接口的实现类如下:

    image.png

    重点看下 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…

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论