在使用 SpringMVC 时,Spring 容器是如何与 Servlet 容器进行交互的?

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 最近都在看小马哥的 Spring 视频教程,通过这个视频去系统梳理一下 Spring 的相关知识点,就在一个晚上,躺床上看着视频快睡着的时候,突然想到当我们在使用 SpringMVC 时,Spring 容器是如何与 Servlet 容器进行交互的?虽然在我的博客上还有几年前写的一些 SpringMVC 相关源码分析,其中关于 Spring 容器如何与 Servlet 容器进行交互并没有交代清楚,于是趁着这个机会,再撸一次 SpringMVC 源码。

最近都在看小马哥的 Spring 视频教程,通过这个视频去系统梳理一下 Spring 的相关知识点,就在一个晚上,躺床上看着视频快睡着的时候,突然想到当我们在使用 SpringMVC 时,Spring 容器是如何与 Servlet 容器进行交互的?虽然在我的博客上还有几年前写的一些 SpringMVC 相关源码分析,其中关于 Spring 容器如何与 Servlet 容器进行交互并没有交代清楚,于是趁着这个机会,再撸一次 SpringMVC 源码。


Spring 容器的加载



可否还记得,当年还没有 Springboot 的时候,在 Tomcat 的 web.xml 中进行面向 xml 编程的青葱岁月?其中有那么几段配置总是令我记忆犹新:


首先是 Spring 容器配置:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring-config.xml</param-value>
</context-param>


其次是 Servlet 容器监听器配置:

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


在 Tomcat 启动时,根据这两段配置,究竟做了什么动作,使得 Tomcat 与 Spring 完美地结合在一起了呢?


首先我们来看下 ContextLoaderListener 监听器的源码:

640.png


我们发现它继承了 ContextLoader,并且实现了 ServletContextListener 接口,下面说下这两个东西的作用:


  1. ContextLoader:正如其名,ContextLoader 可以在启动时载入 IOC 容器;
  2. ServletContextListener:ServletContextListener 接口有两个抽象方法,contextInitialized 和 contextDestroyed,该监听器会结合 Web 容器的生命周期被调,ContextLoaderListener 正是实现了该接口。


因此,ContextLoaderListener 最主要的作用就是在 Tomcat 启动时,根据配置加载 Spring 容器。

640.png

以上就是 ContextLoaderListener 实现 contextInitialized 方法的逻辑,也是加载并初始化 Spring 容器的开始。


org.springframework.web.context.ContextLoader#initWebApplicationContext

640.jpg


以上代码逻辑主要做了以下几个操作:


  1. 调用 createWebApplicationContext 方法创建一个容器,会创建一个 contextClass 类型的容器,如果没有配置,则默认创建 WebApplicationContext 类型的容器;
  2. 将容器强转为 ConfigurableWebApplicationContext 类型;
  3. 调用 configureAndRefreshWebApplicationContext 方法初始化 Spring 容器;
  4. 最后将 Spring 容器,以一个元素的形式保存到 Servlet 容器中,这也就意味着,得到 Servlet 容器,同时也可以得到 Spring 容器。


还发现 Spring 容器保存到 Servlet 容器中的 key 为 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们顺藤摸瓜找到获取 Spring 容器的方法:


org.springframework.web.context.support.WebApplicationContextUtils#getWebApplicationContext

640.jpg


关于这个方法在哪里调用后面有说到。


org.springframework.web.context.ContextLoader#configureAndRefreshWebApplicationContext

640.jpg


以上是 Spring 容器初始化逻辑,其中,CONFIG_LOCATION_PARAM 即是我们在 xml 中配置的 contextConfigLocation 参数:

640.png


同时还会将 Servlet 容器保存到 Spring 容器中,最后调用 refresh 方法进行初始化。

在将 Spring 容器初始化最后以一个元素的形式保存到 Servlet 容器之后,那么 SpringMVC 在初始化时,是如何拿到 Spring 容器的呢?


我们继续看 SpringMVC 初始化是怎么操作的。


SpringMVC 容器的加载



SpringMVC 本质上来讲,就是一个大号的 Servlet,其各种机制都是围绕着一个名叫 DispatcherServlet 的 Servlet 展开的,因此它必然实现了 Servlet 接口,那么在 Tomcat 启动时,它必然会通过 Servlet#init 方法进行初始化动作,我在其调用链路上发现以下方法:


org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext

640.jpg

DispatcherServlet 的父类同样有一个方法,该方法是加载 SpringMVC 容器,即源码中的 webApplicationContext:

640.png

我们发现,rootContext 就是 ContextLoaderListener 加载的 Spring 容器,在这里,它会以父容器的身份保存到 SpringMVC 容器中。


当然,如果用 Springboot 环境,那么默认只会存在一个上下文环境,原因如下:

1、在 Springboot 应用程序启动时,在 SpringBootServletInitializer#onStartup 方法中,会创建一个 rootAppContext 容器,如下:

640.png


我们发现,rootContext 就是 ContextLoaderListener 加载的 Spring 容器,在这里,它会以父容器的身份保存到 SpringMVC 容器中。


当然,如果用 Springboot 环境,那么默认只会存在一个上下文环境,原因如下:

1、在 Springboot 应用程序启动时,在 SpringBootServletInitializer#onStartup 方法中,会创建一个 rootAppContext 容器,如下:

640.png

DispatcherServlet 初始化时,经过 debug 可以看到,rootContext 和 webApplicationContext 是同一个实例对象:

640.png


原因是通过 ContextLoaderListener 加载的上下文环境,通过 ApplicationContextAware 接口自动 set 进来保存到 DispatcherServlet 的 webApplicationContext 变量中了。


在 FrameworkServlet#initWebApplicationContext 方法最后,最终会将 webApplicationContext 注入以一个元素的形式保存到 Servlet 容器中:

640.png


DispatcherServlet 初始化



最终,SpringMVC 初始化会调用该方法:

org.springframework.web.servlet.DispatcherServlet#onRefresh

640.png

DispatcherServlet 初始化时,从 Spring 容器中获取相关 Bean,初始化各种不同的组件,比如初始化 HandlerMapping:

640.png

DispatcherServlet 初始化时,从 Spring 容器中获取相关 Bean,初始化各种不同的组件,比如初始化 HandlerMapping:

相关文章
|
4月前
|
存储 消息中间件 容器
当一个 Pod 中包含多个容器时,容器间共享一些重要的资源和环境,这使得它们能够更有效地协同工作和交互。
当一个 Pod 中包含多个容器时,容器间共享一些重要的资源和环境,这使得它们能够更有效地协同工作和交互。
|
7天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
36 6
|
22天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
32 1
|
1月前
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
1月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
51 0
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
2月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
61 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
3月前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
155 3
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
80 0
|
4月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式