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 方法以及其所在的类。

调用顺序是:

  1. org.apache.catalina.core.StandardWrapperValve#invoke
  2. org.apache.catalina.core.StandardWrapper#allocate
  3. org.apache.catalina.core.StandardWrapper#initServlet
  4. javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
  5. org.springframework.web.servlet.HttpServletBean#init
  6. org.springframework.web.servlet.FrameworkServlet#initServletBean
  7. org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
  8. org.springframework.web.servlet.DispatcherServlet#onRefresh
  9. org.springframework.web.servlet.DispatcherServlet#initStrategies

既然是懒加载,那我们就可以找一找相关的逻辑,判断是否已经加载、初始化,allocate 就是你了,别看了别人了,点击去: org.apache.catalina.core.StandardWrapper#allocate

java 复制代码
    @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 源码:

java 复制代码
	/**
	 * 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 初始化,就做了两件事

  1. 读取配置,并将配置参数值,设置到当前 bean 的属性中
  2. 调用子类初始化

org.springframework.web.servlet.FrameworkServlet#initServletBean 源码:

java 复制代码
	/**
	 * 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 初始化方法,也只做了两件事:

  1. 初始化 webApplicationContext
  2. 初始化 servlet。但空实现,也就是让子类重写

但第二个方法点进去,只是个空实现,在 DispatcherServlet 中,也没有重写,也就是实际未运行任务代码。我们的重点就在 initWebApplicationContext 方法中,DispatcherServlet#onRefresh 也就在是在这个方法中被调用的。

java 复制代码
	/**
	 * 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 方法。 这里有几个疑问点:

  1. rootContext 是谁,父容器?如果是,是什么内容。
  2. createWebApplicationContext 创建的是什么容器,字母意思是 servlet 容器,需要进一步了解。
  3. servlet 容器(mvc)和 spring 容器的关系。

org.springframework.web.servlet.DispatcherServlet#onRefresh 源码:

java 复制代码
	/**
	 * 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 的源码如下:

java 复制代码
	/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>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<String, HandlerMapping> 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 实例。

按类型获取:

java 复制代码
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

按名字获取:

java 复制代码
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 注解,是不是就对上了。

继续翻阅源码注释:

java 复制代码
/**  
* 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<RequestMappingInfo> {

该基类的作用:

用于{@link RequestMappingInfo}定义请求和处理程序方法之间映射的类的抽象基类。

java 复制代码
/**  
* 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 的源码如下:

java 复制代码
	/**
	 * Initialize the HandlerAdapters used by this class.
	 * <p>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<String, HandlerAdapter> 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 类,源码如下:

java 复制代码
/**  
* Extension of {@link AbstractHandlerMethodAdapter} that supports  
* {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.  
*  
* <p>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...

相关推荐
CodeClimb4 分钟前
【华为OD-E卷 - 猜字谜100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
ADRU10 分钟前
设计模式-责任链模式
java·设计模式·责任链模式
heeheeai11 分钟前
kotlin 函数作为参数
java·算法·kotlin
m0_7482548828 分钟前
Spring Boot实现多数据源连接和切换
spring boot·后端·oracle
吴冰_hogan34 分钟前
Java虚拟机(JVM)的类加载器与双亲委派机制
java·开发语言·jvm
程序员shen16161135 分钟前
注意⚠️:矩阵系统源码开发/SaaS矩阵系统开源/抖音矩阵开发优势和方向
java·大数据·数据库·python·php
庄周de蝴蝶1 小时前
一次 MySQL IF 函数的误用导致的生产小事故
后端·mysql
青春男大1 小时前
java队列--数据结构
java·开发语言·数据结构·学习·eclipse
韩数1 小时前
Nping: 支持图表实时展示的多地址并发终端命令行 Ping
后端·rust·github
yzhSWJ1 小时前
mybatisplu设置自动填充
java·spring·tomcat