您的位置:首页 > 文旅 > 美景 > 《学会 SpringMVC 系列 · 剖析初始化》

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

2024/10/5 4:50:41 来源:https://blog.csdn.net/syb513812/article/details/140905856  浏览:    关键词:《学会 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> {@Overridepublic 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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com