《学会 SpringMVC 系列 · 剖析初始化》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

CSDN.gif

写在前面的话

前几篇博文,大致了解了SpringMVC请求流程中的源码分析和扩展运用,为知识连贯性,本篇介绍一下 SpringMVC 初始化过程中的相关源码解读。
和请求流程相比,初始化流程更为简单,主要就是初始化得到一些准备数据,为后续请求过程服务。

相关博文
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》


SpringMVC 初始化

前文提要

前面请求篇介绍的时候,提到 DispatcherServlet 是 SpringMVC 的入口,不管是否为 SpringBoot 项目,都是如此。
从下面这张图很明显可以看出 DispatcherServlet 和 Servlet 的父子关系。
image.png
有过 JavaWeb 开发经验的人应该了解,Servlet 的初始化是 inti 方法。这边先不展开细节,后续再按专栏展开,总之是在 DispatcherServlet、FrameworkServlet、HttpServlet 等类之间反复横跳。
可以先将入口判定为 HttpServletBean#initServletBean。

启动流程

Tips:DispatcherServlet 本质就是一个 Servlet,遵循Servlet的初始化和请求规范。

1、启动 Tomcat
2、解析 web.xml
3、创建并初始化 DispatcherServlet
4、触发 DispatcherServlet 父类的init方法(比如 HttpServletBean#initServletBean)
5、FrameworkServlet#initWebApplicationContext(这步接下来是核心,创建Web上下文)
6、FrameworkServlet#configureAndRefreshWebApplicationContext(配置文件的解析,扫描和加载Bean)
7、DispatcherServlet#initStrategies(初始化HandleMapping等内容,代码如下)

Tips:创建容器的过程类似Spring,没什么特殊的,这边暂不展开。

@Override
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}
/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

最后一步用到了Spring的发布订阅,FrameworkServlet 内部有如下代码,最终触发到了 DispatcherServlet 的 onRefresh。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		FrameworkServlet.this.onApplicationEvent(event);
	}
}

initHandlerMappings

Tips:nitStrategies 里面方法很多,比较重要的,或者说和后续请求流程关联较多的,这里挑选介绍。

如方法的名字,是初始化 HandlerMappings,会加载自定义的,也会加载默认的 HandlerMappings。
自定义场景较少,默认的话是读取 DispatcherServlet.properties 文件里面的,如下所示。

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;

	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}

	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}

	for (HandlerMapping mapping : this.handlerMappings) {
		if (mapping.usesPathPatterns()) {
			this.parseRequestPath = true;
			break;
		}
	}
}

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

image.png
由于 RequestMappingHandlerMapping 的父类,实现了 InitializingBean 接口,如下所示(圈出来的几个东西后续要关注的)。
所以会被在创建 RequestMappingHandlerMapping 的 bean 对象的时候,系统会触发其 afterPropertiesSet 方法
image.png
几经辗转,走到父类AbstractHandlerMethodMapping的如下方法,这里判断isHandler方法,代表符合要求才处理。

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (logger.isTraceEnabled()) {
			logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	if (beanType != null && isHandler(beanType)) {
		detectHandlerMethods(beanName);
	}
}

可以看到RequestMappingHandlerMapping的isHandler是判断包含Controller注解才处理。

protected boolean isHandler(Class<?> beanType) {
	return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);
}

如果类符合要求,进一步就是处理该类的方法,这边会继续判断方法是否包含RequestMapping注解,然后解析注解信息,参考如下代码。
不继续深入展开,有需要再深入。

protected void detectHandlerMethods(Object handler) {
	Class<?> handlerType = (handler instanceof String beanName ?
			obtainApplicationContext().getType(beanName) : handler.getClass());

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 得到一个Map,Key是Method,Value是RequestMappingInfo(注解信息)
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		else if (mappingsLogger.isDebugEnabled()) {
			mappingsLogger.debug(formatMappings(userType, methods));
		}

        //遍历上面的Map,得到新的Map,Key是URL,Value是Method(包含很多信息,MappingRegistry)
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

initHandlerAdapters

前面逻辑基本和HandleMappings一致,触发RequestMappingHandlerAdapter的afterPropertiesSet方法。
这个方法就加载了很多东西,方法子方法的名字大概也可以猜到。
请求流程里面涉及的@ControllerAdvice、消息转换器、参数解析器、返回值处理器等等,都涉及到了,应有尽有。

@Override
public void afterPropertiesSet() {

    //处理加了@ControllerAdvice注解的
	initControllerAdviceCache();

    //初始化消息转换器
	initMessageConverters();

	if (this.argumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.initBinderArgumentResolvers == null) {
		List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
		this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
	}
	if (this.returnValueHandlers == null) {
		List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
		this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
	}
	if (BEAN_VALIDATION_PRESENT) {
		List<HandlerMethodArgumentResolver> resolvers = this.argumentResolvers.getResolvers();
		this.methodValidator = HandlerMethodValidator.from(
				this.webBindingInitializer, this.parameterNameDiscoverer,
				methodParamPredicate(resolvers, ModelAttributeMethodProcessor.class),
				methodParamPredicate(resolvers, RequestParamMethodArgumentResolver.class));
	}
}

回到请求流程

初始化流程没有什么复杂的,不一一展开,浪费大家时间。
经过上面两个初始化方法,大概拿到了一些准备数据,现在再回到请求流程的核心方法 DispatcherServlet#doDispatch,代码如下所示,其实就是三步骤:找HandleMapping、找HandlerAdapter、执行HandlerAdapter的handle方法,这个过程中,用到的都是各种初始化后得到的东西。

// 找出处理这个请求的执行链对象 HandlerExecutionChain,包含方法和拦截器
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
	noHandlerFound(processedRequest, response);
	return;
}

// 根据 HandlerMethod 获取处理器 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 执行实际的处理器处理请求,这步骤是最核心的
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

总结陈词

此篇文章介绍了SpringMVC 初始化相关的分析,仅供学习参考。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

CSDN_END.gif

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战神刘玉栋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值