Spring IOC容器在Web环境中是如何启动的(源码级剖析)?

文章目录

在Java Web 应用开发中,Spring的IOC容器同样扮演着核心角色。Spring IOC(控制反转)容器的启动过程需要与 Web 容器【Servlet容器】(如 Tomcat、Jetty)的生命周期集成。本文将深入剖析这一过程的核心机制。

一、Web 环境中的 Spring MVC 框架

在Web环境中,业内主流的是Spring MVC框架, 但它是建立在Spring IoC容器基础上的。所以想要深入了解Spring MVC框架,首先要了解 Spring IoC 容器是如何在 Web 环境中被载入并生效的

  • Spring IoC是Spring框架的基石,是一个独立的模块,它并不能直接在 Web 容器中发挥作用;要在Web环境中使用IoC容器,则需要 Spring 为它设计一个启动过程,好引导它在web环境中启动。

  • 具体说来,这个启动过程是和 Web 容器【Servlet容器】(如 Tomcat、Jetty)的生命周期的启动过程集成在一起的。在这个过程中,一方面处理 Web 容器的启动,另一方面通过设计特定的Web容器过滤器,将IoC容器载人到Web环境中并将其初始化

  • 等启动过程执行完成后Spring IoC容器就能正常工作;而Spring MVC是建立在IoC容器的基础上的,这样才能建立起完整MVC框架的运行机制,从而可以接收、响应从Web容器传递的HTTP请求。

下面就以 TomcatWeb 容器的进行分析这个启动过程

二、Web 应用部署描述配置

在 Tomcat 中,web.xml 是应用的部署配置文件。在Spring MVC项目中的 web.xml 中经常能看到与 Spring 相关的部署配置。

传统配置(web.xml):

xml 复制代码
<!-- DispatcherServlet配置 -->
<servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/*</url-pattern>
</servlet-mapping>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 根容器初始化 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

要点解析:

web.xml 这个部署描述文件中,定义了一些对象:

  • DispatcherServlet:是 Spring MVC 的 DispatcherServlet,且是一个Servlet对象。起着分发请求的作用,是MVC框架中很重要的一个类。
  • servlet > init-param:用来指定 Spring MVC 容器读取Web Bean定义的XML文件路径,这里配置文件指定为/WEB-INF/app-context.xml
  • servlet-mapping:为这个 DispatcherServlet 定义了对应的URL映射,以指定这个Servlet需要处理的HTTP请求范围。
  • context-param 参数用来指定 Spring IoC 容器读取Bean定义的XML文件路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml
  • ContextLoaderListener核心类 ,是 Spring MVC 的启动类,被定义为一个监听器,它是与Web服务器的生命周期相关联的,是它负责完成 IoC 容器在 Web 环境中的启动工作

核心机制

  1. 关键组件 :通过DispatchServlet(请求转发器)和ContextLoaderListener(容器初始化监听器)实现与Web容器的对接。
  2. 耦合机制 :基于ServletContext实现与Web容器的解耦,ServletContext作为servlet规范的体现,既是容器与应用的桥梁,又为Spring IoC容器提供宿主环境。
  3. 容器体系构建ContextLoaderListener负责初始化建立IoC容器体系,随后初始化DispatchServlet作为请求处理器。
  4. 完整流程:这两个组件协同工作,使基于IoC容器的Spring MVC能够完成HTTP请求的接收、处理和响应。

下面是 Servlet 3.0+ 规范后我们可以直接使用Java配置类的方式实现web.xml相同功能;

Java配置类(Servlet 3.0+):

java 复制代码
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class}; 
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};  
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

下面我们看一下IoC容器在Web环境中的启动及代码实现。

三、核心启动流程详解

1. 启动流程图

2. ★容器初始化入口:ContextLoaderListener

初始化入口类ContextLoaderListener,源码位置:org.springframework.web.context.ContextLoaderListener


核心要点

  • 可以看到它实现了 ServletContextListener 接口,这个接口是 Servlet API 中定义的,提供了与 Servlet 生命周期相结合的回调;它是在Web容器中配置的监听器,作为监听器当监听到Web服务器(Tomcat)启动时,它的 contextInitialized()方法会被调用从而在这触发载入 IoC 容器
  • 另外,它还继承了 ContextLoader,具体的载入 IoC 容器的过程是由 ContextLoader 来完成的。

3. ★容器创建核心:ContextLoader

创建核心类ContextLoader,源码位置:org.springframework.web.context.ContextLoader




核心要点

  1. 创建Web根上下文容器实例:
    • 创建在 ServletContext 中存储的Web根上下文容器 (Spring IOC容器);会根据ServletContext中获取contextClass参数的配置来创建指定的IoC容器,默认使用XmlWebApplicationContext作为在Web环境中使用的IoC容器。
    • 直接通过反射实例化需要的IoC容器
  2. 载入根上下文的双亲上下文:
    • 判断当前Web根上下文的Parent contextnull时为当前 Web 应用的根上下文设置一个父上下文,从而支持上下文的继承和共享,适用于需要分层或共享资源的复杂应用场景。
  3. 核心初始化方法(配置并刷新容器):
    • 为当前Web应用容器配置容器Web环境
    • 加载配置文件(从web.xmlcontextConfigLocation参数);
    • ServletContextServletConfig 中的属性添加到容器环境中,以便在后续的应用上下文初始化过程中使用;
    • 扩展点,可对 ConfigurableWebApplicationContext 进行自定义配置,在上下文刷新(refresh())之前被调用;
    • IoC容器的初始化(调用AbstractApplicationContext.refresh()

四、扩展:Web容器中的上下文设计

Spring框架为Web应用提供了专用的上下文(IOC容器)的扩展接口类 WebApplicationContext 来满足Web环境中启动过程的需要,其主要继承关系如下:

WebApplicationContext

核心功能点:

WebApplicationContext 作为 Spring 框架中一个核心接口 ,主要用于为 Web 应用程序提供配置。它扩展了 ApplicationContext 接口,增加了与 Web 环境相关的功能。以下是其主要作用:

  1. 提供Web容器 ServletContext 的访问
    允许访问标准的 Servlet APIServletContext 对象,用于与底层 Web 容器交互。
  2. 支持 Web 特定的作用域
    定义了 Web 特定的作用域标识符,例如:
    • SCOPE_REQUEST:请求作用域。
    • SCOPE_SESSION:会话作用域。
    • SCOPE_APPLICATION:全局 Web 应用作用域。
  3. 与 Web 环境相关的 Bean 定义
    提供了与 ServletContext 和初始化参数相关的 Bean 名称,例如:
    • SERVLET_CONTEXT_BEAN_NAMEServletContext 的 Bean 名称。
    • CONTEXT_PARAMETERS_BEAN_NAMEServletContext 初始化参数的 Bean 名称。
    • CONTEXT_ATTRIBUTES_BEAN_NAMEServletContext 属性的 Bean 名称。
  4. 支持层次化上下文
    Web 应用程序上下文是分层的,整个应用程序有一个根上下文,每个 Servlet(如 Spring MVC 的 DispatcherServlet)有自己的子上下文。
  5. ServletContextAware 的集成
    自动检测实现了 ServletContextAware 接口的 Bean,并调用其 setServletContext 方法。
源码解析

XmlWebApplicationContext

从前面类继承关系图中,可以看到前面了解到的默认Web容器实现XmlWebApplicationContext,它继承自实现了扩展接口类 WebApplicationContext的类 。在 ApplicationContext 的基础上,增加了对 Web 环境 和 XML 配置定义 的处理。

XmlWebApplicationContext 的初始化过程中,Web容器(Servlet容器)中的IoC容器随之被建立起来,具体过程则是从 refresh() 方法入口的, 这里不做过多解析。

下面主要针对XmlWebApplicationContext源码了解学习下。

源码解析

从上面的源代码中可以看到,在 XmlWebApplicationContext 中并没有多少功能点,主要处理了如何在Web环境中获取BeanDefinition信息,这是因为其继承的父类已经具备基础上下文功能(IOC容器的能力)。

总结一下就是:

  • ‌继承体系基础
    • 通过继承AbstractRefreshableConfigApplicationContext等父类已具备基础上下文功能
    • 核心挑战转为Web环境下BeanDefinition资源的定位与加载
  • 资源获取机制
    • 解析web.xml中配置的contextConfigLocation参数确定配置文件路径
    • 将ServletContext路径转换为Spring可识别的Resource对象
  • 定义加载流程
    • 使用XmlBeanDefinitionReader读取XML配置并解析BeanDefinition
    • 加载过程与XmlFileSystemBeanFactory类似,但需适配Web资源路径格式
  • 初始化完成
    • 通过refresh()方法触发完整的容器初始化生命周期
    • 最终形成包含所有BeanDefinition的可运行上下文环境

该上下文(IOC容器)的设计完美的实现了Web环境与标准IoC容器初始化流程的无缝衔接。

五、总结与最佳实践

通过源码分析,我们揭示以下关键机制:

  1. Web上下文容器启动 :由ContextLoaderListener监听Web容器(Tomcat)的启动来创建初始化web应用的根上下文(父IOC容器)
  2. Web上下文容器刷新refresh()方法是容器初始化的核心入口,继承自父类的功能实现,包含12个关键步骤(BeanFactory创建、后处理器注册、单例初始化等)
  3. Web上下文父子容器交互 :子容器通过setParent()方法引用父容器;Bean查找时通过getParentBeanFactory()实现委托

深入理解这些底层机制,将帮助开发者更好地诊断启动问题、优化应用性能,并为复杂场景(如动态注册Bean)提供解决方案。


ContextLoaderListener Diagram


End!

相关推荐
青云交11 分钟前
Java 大视界 -- 基于 Java 的大数据分布式存储在云计算数据中心数据管理与调度中的应用(348)
java·大数据·分布式存储·高频交易·银行灾备·云计算数据中心·大数据分布式
GuGu202422 分钟前
Lambda表达式和比较器的学习
java
超级码.里奥.农29 分钟前
零基础 “入坑” Java--- 十二、抽象类和接口
java·开发语言
野蛮人6号39 分钟前
黑马点评系列问题之p70postman报错“服务器异常”
java·redis·黑马点评
在软件大道骑行的小石1 小时前
别怪 GC 不回收你:可能是你的内部类太粘人了
java·后端
共享家95271 小时前
Linux 自旋锁
java·前端·数据库
zhangyifang_0091 小时前
SpringJDBC源码初探-JdbcTemplate类
spring·jdbc
程序猿阿越1 小时前
Kafka源码(二)分区新增和重分配
java·后端·源码阅读
超龄超能程序猿1 小时前
Word 文档合并利器:基于 org.docx4j 的 Java 实现全解析
java·spring·spring cloud·c#·word·maven
橙序员小站2 小时前
Java 性能难排查?JFR 到底能帮上什么忙?
java·性能优化