当前位置: 首页 > news >正文

《学会 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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 学习分享:电商平台 API 接入技术要点深度剖析
  • 分享一个简单线性dp
  • 2024 年华数杯全国大学生数学建模竞赛题目B 题 VLSI 电路单元的自动布局完整成品文章分享
  • C++——哈希结构
  • 中国县城建设统计年鉴(2015-2022年)
  • 基础算法之模拟
  • RK3568笔记五十二:HC-SR04超声波模块驱动测试
  • modbus控制传感器
  • PHP单例模式详解及应用
  • 使用Python库开发Markdown编辑器并将内容导出为图片
  • 学习笔记-优化问题
  • 正点原子imx6ull-mini-Linux驱动之Linux SPI 驱动实验(22)
  • Netty二
  • 【从零开始一步步学习VSOA开发】搭建VSOA运行环境
  • rust和c传递字符串的七种方法--翻译
  • ----------
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 08.Android之View事件问题
  • 11111111
  • 77. Combinations
  • CentOS7简单部署NFS
  • ERLANG 网工修炼笔记 ---- UDP
  • iOS编译提示和导航提示
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • 闭包--闭包作用之保存(一)
  • 大主子表关联的性能优化方法
  • 警报:线上事故之CountDownLatch的威力
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 说说动画卡顿的解决方案
  • 算法-图和图算法
  • 用mpvue开发微信小程序
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • 通过调用文摘列表API获取文摘
  • ​如何防止网络攻击?
  • #Datawhale AI夏令营第4期#AIGC文生图方向复盘
  • #QT项目实战(天气预报)
  • $.proxy和$.extend
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (全注解开发)学习Spring-MVC的第三天
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (四)库存超卖案例实战——优化redis分布式锁
  • (转)菜鸟学数据库(三)——存储过程
  • (转载)从 Java 代码到 Java 堆
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .htaccess配置常用技巧
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .netcore 获取appsettings
  • @vue/cli 3.x+引入jQuery
  • [bzoj1038][ZJOI2008]瞭望塔
  • [BZOJ1053][HAOI2007]反素数ant
  • [C/C++]数据结构 栈和队列()