Spring系列-SpringMvc父子容器启动原理解析

1、Spring整合SpringMVC

特性:

说到Spring整合SpringMVC唯一的体现就是父子容器:

  • 通常我们会设置父容器(Spring)管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean .
  • 子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。

实现:

相信大家在SSM框架整合的时候都曾在web.xml配置过这段:

<!--spring 基于web应用的启动-->
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--设置配置文件的路径--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value>
</init-param><!--设置启动即加载--><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern>

但是它的作用是什么知道吗?

0

有人可能只知道DispatcherServlet叫前端控制器,是SpringMVC处理前端请求的一个核心调度器

那它为什么能处理请求?处理之前做了什么准备工作呢?又是怎么和Spring结合起来的呢?

为什么有了DispatcherServlet还要个ContextLoaderListener, 配一个不行吗?干嘛要配俩啊?

看完本文你就会有答案!

0

还有人可能会觉得, 我现在都用SpringBoot开发, 哪还要配这玩意.......

0

这就是典型的SpringBoot使用后遗症,SpringBoot降低了使用难度,但是从某种程度来说,也让初级的程序员变得更加小白,把实现原理都隐藏起来了而我们只管用,一旦涉及扩展就束手无策。

那当然我们今天不讲SpringBoot,我们今天用贴近SpringBoot的方式来讲SpringMVC。也就是零配置(零xml)的放式来说明SpringMVC的原理!!

此方式作为我们本文重点介绍,也是很多人缺失的一种方式, 其实早在Spring3+就已经提供, 只不过我们直到SpringBoot才使用该方式进行自动配置, 这也是很多人从xml调到SpringBoot不适应的原因, 因为你缺失了这个版本。 所以我们以这种方式作为源码切入点既可以理解到XML的方式又能兼顾到SpringBoot的方式 。

2、零配置SpringMVC实现方式:

那没有配置就需要省略掉web.xml 怎么省略呢?

在Servlet3.0提供的规范文档中可以找到2种方式:

  • 注解的方式
  1. @WebServlet
  2. @WebFilter
  3. @WebListener

但是这种方式不利于扩展, 并且如果编写在jar包中tomcat是无法感知到的。

  • SPI的方式

在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:

也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范

SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 其实很好理解的一个东西:

其实就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录(META-INF/services)放上以接口全类名 为命名的文件, 文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法, 从而完成扩展。

ok 那我们知道了SPI是什么,我们是不是可以在Web应用中,在Servlet的SPI放入对应的接口文件:

0

放入实现类:

0

通过ServletContext就可以动态注册三大组件:以Servlet注册为例:

public class TulingSpringServletContainerInitializer extends SpringServletContainerInitializer {@Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {// 通过servletContext动态添加ServletservletContext.addServlet("spiServlet", new HttpServlet() {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("spiServlet--doGet");}}).addMapping("/spiServlet.do");}

 当然在SpringMVC中, 这个接口文件和实现类都把我们实现好了,甚至ContextLoaderListener和DispatcherServlet都帮我们注册好了,我们只要让他生效,来,看看他是怎么做的:

3、实现基于SPI规范的SpringMVC

TulingStarterInitializer

  • 此类继承AbstractAnnotationConfigDispatcherServletInitializer 这是个啥? 待会我们讲原理来介绍
  • getRootConfigClasses 提供父容器的配置类
  • getServletConfigClasses 提供子容器的配置类
  • getServletMappings 设置DispatcherServlet的映射
public  class TulingStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** 方法实现说明:IOC 父容器的启动类* @author:xsls* @date:2019/7/31 22:12*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{RootConfig.class};}/*** 方法实现说明 IOC子容器配置 web容器配置* @author:xsls* @date:2019/7/31 22:12*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{WebAppConfig.class};}/*** 方法实现说明* @author:xsls* @return: 我们前端控制器DispatcherServlet的拦截路径* @exception:* @date:2019/7/31 22:16*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};

RootConfig

  • 父容器的配置类 =以前的spring.xml
  • 扫描的包排除掉@Controller
@Configuration
@ComponentScan(basePackages = "com.tuling",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value={RestController.class,Controller.class}),@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {}

WebAppConfig

  • 子容器的配置类 =以前的spring-mvc.xml
  • 扫描的包:包含掉@Controller
@Configuration
@ComponentScan(basePackages = {"com.tuling"},includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // ≈<mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{/*** 配置拦截器* @return*/@Beanpublic TulingInterceptor tulingInterceptor() {return new TulingInterceptor();}/*** 文件上传下载的组件* @return*/@Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setDefaultEncoding("UTF-8");multipartResolver.setMaxUploadSize(1024*1024*10);return multipartResolver;}/*** 注册处理国际化资源的组件* @return*/
/* @Beanpublic AcceptHeaderLocaleResolver localeResolver() {AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();return acceptHeaderLocaleResolver;}*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tulingInterceptor()).addPathPatterns("/*");}/*** 方法实现说明:配置试图解析器* @author:xsls* @exception:* @date:2019/8/6 16:23*/@Beanpublic InternalResourceViewResolver internalResourceViewResolver() {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setSuffix(".jsp");viewResolver.setPrefix("/WEB-INF/jsp/");return viewResolver;}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new MappingJackson2HttpMessageConverter());}

自己去添加个Controller进行测试

OK, 现在可以访问你的SpringMVC了

4、SPI的方式SpringMVC启动原理

接着我们来看看SPI方式的原理是什么:

SpringMVC 大致可以分为 启动 和请求 2大部分, 所以我们本文先研究启动部分

流程图:

源码流程

  1. 外置Tomcat启动的时候通过SPI 找到我们应用中的/META-INF/service/javax.servlet.ServletContainerInitializer

0

  1. 调用SpringServletContainerInitializer.onStartUp()

0

    1. 调用onStartUp()前会先找到@HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。
    2. 重点关注这组类:他们组成了父子容器

0

  1. 找到所有WebApplicationInitializer的实现类后, 不是接口、不是抽象则通过反射进行实例化(所以,你会发现内部实现类都是抽象的,你想让其起作用我们必须添加一个自定义实现类,在下文提供我的自定义实现类)
  2. 调用所有上一步实例化后的对象的onStartup方法

0

0

1. 首先来到AbstractDispatcherServletInitializer#onStartup再执行super.onStartup(servletContext);

@Override
public void onStartup(ServletContext servletContext) throws ServletException {//实例化我们的spring root上下文super.onStartup(servletContext);//注册我们的DispatcherServlet   创建我们spring web 上下文对象registerDispatcherServlet(servletContext);

 创建父容器——ContextLoaderListener

2.父类AbstractContextLoaderInitializer#onStartup执行registerContextLoaderListener(servletContext);

  1. createRootApplicationContext()该方法中会创建父容器
    1. 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
      1. 调用getRootConfigClasses();方法获取父容器配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
      2. 创建父容器,注册配置类

0

  1. 会创建ContextLoaderListener并通过ServletContext注册

0

看完大家是不是感觉跟我们XML的配置ContextLoaderListener对上了:

0

创建子容器——DispatcherServlet

3.回到AbstractDispatcherServletInitializer#onStartup再执行registerDispatcherServlet(servletContext);

0

registerDispatcherServlet方法说明:

  1. 调用createServletApplicationContext创建子容器
    1. 该方法是抽象方法,实现类是AbstractAnnotationConfigDispatcherServletInitializer
      1. 创建子容器(下图很明显不多介绍)
      2. 调用抽象方法:getServletConfigClasses();获得配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的配置类 )
      3. 配置类除了可以通过ApplicationContext()构造函数的方式传入 , 也可以通过这种方式动态添加,不知道了吧~

0

  1. 调用createDispatcherServlet(servletAppContext);创建DispatcherServlet
  2. 设置启动时加载:registration.setLoadOnStartup(1);
  3. 调用抽象方法设置映射路径:getServletMappings()(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )

看完大家是不是感觉跟我们XML的配置DispatcherServlet对上了

0

4. 初始化ContextLoaderListener

0

ContextLoaderListener加载过程比较简单:

外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 进行初始化

  1. xml的方式下会判断容器为空时创建父容器
  2. 在里面会调用父容器的refresh方法加载
  3. 将父容器存入到Servlet域中供子容器使用

0

5. 初始化DispatcherServlet

0

可以看到流程比ContextLoaderListener流程更多

外置tomcat会帮我们调用DispatcherServlet#init()   进行初始化--->重点关注:initWebApplicationContext方法

  1. getWebApplicationContext(getServletContext())获得父容器(从之前的Servlet域中拿到)
  2. cwac.setParent(rootContext);给子容器设置父容器
  3. 调用configureAndRefreshWebApplicationContext(cwac);

0

    1. 注册一个监听器(该监听会初始化springmvc所需信息)
      1. ContextRefreshedEvent可以看到该监听器监听的是容器refreshed事件, 会在finishRefresh中发布
    2. 刷新容器

0

当执行refresh 即加载ioc容器 完了会调用finishRefresh():

  1. publishEvent(new ContextRefreshedEvent(this));发布ContextRefreshedEvent事件
  2. 触发上面的ContextRefreshListener监听器:

---->FrameworkServlet.this.onApplicationEvent(event);

-------->onRefresh(event.getApplicationContext());

-------------->initStrategies(context);

protected void initStrategies(ApplicationContext context) {//初始化我们web上下文对象的 用于文件上传下载的解析器对象initMultipartResolver(context);//初始化我们web上下文对象用于处理国际化资源的initLocaleResolver(context);//主题解析器对象初始化initThemeResolver(context);//初始化我们的HandlerMappinginitHandlerMappings(context);//实例化我们的HandlerAdaptersinitHandlerAdapters(context);//实例化我们处理器异常解析器对象initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);//给DispatcherSerlvet的ViewResolvers处理器initViewResolvers(context);initFlashMapManager(context);

这里面的每一个方法不用太细看, 就是给SpringMVC准备初始化的数据, 为后续SpringMVC处理请求做准备

基本都是从容器中拿到已经配置的Bean(RequestMappingHandlerMapping、RequestMappingHandlerAdapter、HandlerExceptionResolver )放到dispatcherServlet中做准备:

0

0

0

...

但是这些Bean又是从哪来的呢?? 来来来, 回到我们的WebAppConfig

我们使用的一个@EnableWebMvc

  1. 导入了DelegatingWebMvcConfiguration@Import(DelegatingWebMvcConfiguration.class)
  2. DelegatingWebMvcConfiguration的父类就配置了这些Bean
  3. 而且我告诉你SpringBoot也是用的这种方式,

0

总结

  1. Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象
    1. 同时创建父子容器
      1. 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
      2. 在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类
  2. Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法, 执行容器refresh进行加载
  3. 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类+@EnableWebMvc配置(DelegatingWebMvcConfiguration)——可实现WebMvcConfigurer进行定制扩展)
    1. RequestMappingHandlerMapping,它会处理@RequestMapping 注解
    2. RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
    3. HandlerExceptionResolver 错误视图解析器
    4. addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
    5. 等....
  4. 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找: 详情见AbstractBeanFactory#doGetBean方法

 

/** * 一般情况下,只有Spring 和SpringMvc整合的时才会有父子容器的概念, * 作用:* 比如我们的Controller中注入Service的时候,发现我们依赖的是一个引用对象,那么他就会调用getBean去把service找出来* 但是当前所在的容器是web子容器,那么就会在这里的 先去父容器找*/
BeanFactory parentBeanFactory = getParentBeanFactory();
//若存在父工厂,且当前的bean工厂不存在当前的bean定义,那么bean定义是存在于父beanFacotry中
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {//获取bean的原始名称String nameToLookup = originalBeanName(name);//若为 AbstractBeanFactory 类型,委托父类处理if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {//  委托给构造函数 getBean() 处理return (T) parentBeanFactory.getBean(nameToLookup, args);}else {// 没有 args,委托给标准的 getBean() 处理return parentBeanFactory.getBean(nameToLookup, requiredType);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/338689.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【惯性传感器imu】—— WHEELTEC的惯导模块的imu的驱动安装配置和运行

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、IMU驱动安装1. 安装依赖2. 源码的下载3. 编译源码(1) 配置固定串口设备(2) 修改luanch文件(3) 编译 二、启动IMU1. 运行imu2. 查看imu数据 总结 前言 WHEE…

【C++进阶】深入STL之string:模拟实现走进C++字符串的世界

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C模板入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀STL之string &#x1f4d2;1. string…

图解 Python 编程(10) | 错误与异常处理

&#x1f31e;欢迎来到Python的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年6月2日&…

uni-app全局弹窗的实现方案

背景 为了解决uni-app 任意位置出现弹窗 解决方案 一、最初方案 受限于uni-app 调用组件需要每个页面都引入注册才可以使用&#xff0c;此方案繁琐&#xff0c;每个页面都要写侵入性比较强 二、改进方案 app端&#xff1a;新建一个页面进行跳转&#xff0c;可以实现伪弹窗…

认识微服务,认识Spring Cloud

1. 介绍 本博客探讨的内容如下所示 什么是微服务&#xff1f;什么是springcloud&#xff1f;微服务和springcloud有什么关系&#xff1f; 首先&#xff0c;没有在接触springcloud之前&#xff0c;我写的项目都是单体结构&#xff0c; 但随着网站的用户量越来越大&#xff0c;…

list的简单模拟实现

文章目录 目录 文章目录 前言 一、使用list时的注意事项 1.list不支持std库中的sort排序 2.去重操作 3.splice拼接 二、list的接口实现 1.源码中的节点 2.源码中的构造函数 3.哨兵位头节点 4.尾插和头插 5.迭代器* 5.1 迭代器中的operator和-- 5.2其他迭代器中的接口 5.3迭代器…

【TCP协议中104解析】wireshark抓取流量包工具,群殴协议解析基础

Tcp ,104 ,wireshark工具进行解析 IEC104 是用于监控和诊断工业控制网络的一种标准&#xff0c;而 Wireshark则是一款常用的网络协议分析工具&#xff0c;可以用干解析TEC104 报文。本文将介绍如何使用 Wireshark解析 IEC104报文&#xff0c;以及解析过 程中的注意事项。 一、安…

C语言-01_HelloWord

文章目录 1.C程序运行机制2.HelloWorld的剖析① main()② 函数体③ printf()④ 标准库、头文件 3.输出3.1 printf()标准格式3.2 占位符3.3 输出格式 1.C程序运行机制 过程1&#xff1a;编辑 编写C语言源程序代码&#xff0c;并已文件的形式存储到磁盘中。源程序文件以“.c”作…

dubbo复习:(19)dubbo 和spring整合(老古董)

一、服务端依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM…

【Linux】Linux工具——gcc/g++

1.使用vim更改信用名单——sudo 我们这里来补充sudo的相关知识——添加信任白名单用户 使用sudo就必须将使用sudo的那个账号添加到信用名单里&#xff0c;而且啊&#xff0c;只有超级管理员才可以添加 信用名单在/etc/sudoers里 我们发现它的权限只是可读啊&#xff0c;所以…

cocos入门5:编辑器界面介绍

Cocos Creator是一款功能强大的跨平台游戏开发工具&#xff0c;其编辑器界面设计直观易用&#xff0c;提供了从资源管理、场景编辑到脚本编写等一站式解决方案。下面是对Cocos Creator编辑器界面的详细介绍&#xff1a; 一、界面布局 Cocos Creator编辑器界面通常包含以下几个…

渲染100为什么是高性价比网渲平台?渲染100邀请码1a12

市面上主流的网渲平台有很多&#xff0c;如渲染100、瑞云、炫云、渲云等&#xff0c;这些平台各有特色和优势&#xff0c;也都声称自己性价比高&#xff0c;以渲染100为例&#xff0c;我们来介绍下它的优势有哪些。 1、渲染100对新用户很友好&#xff0c;注册填邀请码1a12有3…

IDeal下的SpringBoot项目部署

一、首先找到自己的sql文件&#xff0c;没有就从数据库挪进来 二、在Maven下打包一下&#xff08;点击package&#xff09;&#xff0c;看到BUILD SUCCESS就是打包好了 三、将上面两个文件分别挪到 linux 中对应的文件&#xff0c;没有就创建一个&#xff08;我的是spring_blog…

常见算法(基本查找、二分查找、分块查找冒泡、选择、插入、快速排序和递归算法)

一、常见算法-01-基本、二分、插值和斐波那契查找 1、基本查找/顺序查找 需求1&#xff1a;定义一个方法利用基本查找&#xff0c;查询某个元素是否存在 数据如下&#xff1a;{131&#xff0c;127&#xff0c;147&#xff0c;81&#xff0c;103&#xff0c;23&#xff0c;7&am…

WordPress子比主题美化-首页动态的图片展示

WordPress子比主题首页动态的图片展示 WordPress子比主题首页添加动态的图片展示&#xff0c;其他程序也可以用&#xff0c;复制代码到相应位置即可&#xff0c;也可作为指定分类&#xff0c;重点内容等&#xff0c;可以适合各个场景&#xff0c;需要的自取。 图片展示: 教程…

Spring源码之BeanDefinition的加载

Spring源码之BeanFactory和BeanDefinition BeanFactory和BeanDefinitionBeanFactoryBeanDefinition源码分析创建AnnotationConfigApplicationContext对象注册配置类refresh方法 BeanFactory和BeanDefinition BeanFactory BeanFactory是Spring提供给外部访问容器的根接口&…

MVC和MVVM

MVC Model层&#xff1a;用于处理应用程序数据逻辑的部分&#xff0c;通常负责在数据库中存取数据 View&#xff08;视图&#xff09;处理数据显示的部分。通常视图是依据模型数据创建的 Controller&#xff08;控制器&#xff09;是处理用户交互的部分。通常控制器负责从视…

Yolov10笔记

一、前言 清华大学团队设计的Yolov10. 在这项工作中&#xff0c;我们主要从后处理和模型结构两方面进一步优化YOLO系列模型的性能和延迟平衡。我们首先为YOLO引入了端到端训练的一致双重分配&#xff0c;这在大大降低推理延迟的情况下保证了性能。此外&#xff0c;我们针对YOLO…

养生与健康|一起跟随林曦老师养个元气满满

暄桐是一间传统美学教育教室&#xff0c;创办于2011年&#xff0c;林曦是创办人和授课老师&#xff0c;教授以书法为主的传统文化和技艺&#xff0c;皆在以书法为起点&#xff0c;亲近中国传统之美&#xff0c;以实践和所得&#xff0c;滋养当下生活。    在暄桐教室的六阶…

XCP协议系列介绍02-基于ASAP2 Tool-Set生成A2l介绍

本文框架 1. 前言2. ASAP2 Tool-Set系统介绍2.1 ASAP2 Creator介绍2.2 ASAP2 Updater介绍2.3 ASAP2 Merger介绍2.4 ASAP2 Comparer及Checker介绍2.5 ASAP2 Modifier介绍2.6 ASAP2 Studio介绍 3. 项目实操说明3.1 项目实操建议3.2 工具下载地址及使用 1. 前言 在XCP观测及标定整…