ClassPathXmlApplicationContext启动源码解析

介绍

  • Spring官网对容器的介绍和使用

  • ClassPathXmlApplicationContext是学习Spring入门依赖接触的第一个容器类。

  • ClassPathXmlApplicationContext是 Spring 框架中用于加载 XML 配置文件来创建应用上下文的类 。它在 Spring 的 IoC(控制反转)容器体系中扮演重要角色,属于ApplicationContext类型的常用容器

用法示例

+ 在对应的 XML 配置文件中,定义相关 Bean :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.cloud.domain.User"></bean>

</beans>
  • 使用时,通常会传入 XML 配置文件路径来创建实例,进而获取配置文件中定义的 Bean 。例如:
public class ClassPathApplicationContextTest {
    public static void main(String[] args) {
        // 创建 ClassPathXmlApplicationContext 实例,加载指定的 XML 配置文件
        ClassPathXmlApplicationContext xmlAppContext = new ClassPathXmlApplicationContext("spring-bean.xml");
        // 从容器中获取名为 user 的 Bean 实例
        User service = (User)xmlAppContext.getBean("user");
        service.test();
    }
}

构造函数

构造函数:有多个重载构造函数ClassPathXmlApplicationContext 是 Spring 框架中常用的应用上下文实现类,用于从类路径加载 XML 配置文件并创建应用上下文。以下是其常见构造函数及其功能介绍:

1. 单路径构造函数

public ClassPathXmlApplicationContext(String configLocation) throws BeansException
  • 功能:从指定的类路径加载单个 XML 配置文件,并创建应用上下文。

  • 参数

    • configLocation:XML 配置文件的路径(如 applicationContext.xml)。
  • 特点

    • 配置文件路径支持类路径前缀(如classpath:),但默认即从类路径加载。
    • 加载后自动刷新容器(调用 refresh()),初始化所有单例 Bean。

2. 多路径构造函数

public ClassPathXmlApplicationContext(String... configLocations) throws BeansException
  • 功能:从多个类路径加载 XML 配置文件,并合并为一个应用上下文。

  • 参数

    • configLocations:多个 XML 配置文件的路径数组(如 {"beans1.xml", "beans2.xml"})。
  • 特点

    • 多个配置文件中的 Bean 定义会合并,相同 ID 的 Bean 后加载的会覆盖先加载的。
    • 同样会自动刷新容器。

3. 带父上下文的单路径构造函数

public ClassPathXmlApplicationContext(String configLocation, ApplicationContext parent) throws BeansException
  • 功能:创建具有父上下文的应用上下文,继承父上下文中的 Bean 定义。

  • 参数

    • configLocation:XML 配置文件路径。
    • parent:父上下文(如 new AnnotationConfigApplicationContext(ParentConfig.class))。
  • 特点

    • 子上下文可访问父上下文中的 Bean,但父上下文无法访问子上下文中的 Bean。
    • 父子上下文的环境配置(如属性源、激活的 profile)会自动合并。

4. 带刷新控制的多路径构造函数

public ClassPathXmlApplicationContext(
    String[] configLocations, 
    boolean refresh, 
    @Nullable ApplicationContext parent
) throws BeansException
  • 功能:最完整的构造函数,支持自定义配置文件、刷新行为和父上下文。
  • 参数
    • configLocations:XML 配置文件路径数组。
    • refresh:是否自动刷新容器(true 表示立即刷新,false 则需手动调用 refresh())。
    • parent:父上下文(可为 null)。
  • 场景
    • 当需要手动控制容器初始化时机时(如先添加额外配置再刷新),可设置 refresh = false

示例对比

构造函数配置文件数量父上下文自动刷新
ClassPathXmlApplicationContext(String)单个
ClassPathXmlApplicationContext(String[])多个
ClassPathXmlApplicationContext(String, ApplicationContext)单个
ClassPathXmlApplicationContext(String[], boolean, ApplicationContext)多个可选可选

注意事项

  1. 路径格式
    • 推荐使用 classpath: 前缀(如 "classpath:config/applicationContext.xml"),避免歧义。
    • 支持通配符(如 "classpath*:config/*.xml"),可匹配多个文件。
  2. 刷新限制
    • 容器刷新后不能重复刷新,需创建新的上下文实例。
  3. 父子上下文关系
    • 父子上下文共享同一个环境(Environment),但 Bean 定义相互隔离。

通过选择合适的构造函数,开发者可以灵活控制应用上下文的加载方式、配置文件组合以及父子上下文关系,满足不同场景下的依赖注入需求。

启动源码流程

UML类图

源码解析

源码执行流程
一、容器初始化流程(构造器调用)
1. 客户端调用入口
Client->>ClassPathXmlApplicationContext: ClassPathXmlApplicationContext("config.xml")
  • 动作:客户端通过构造器传入配置文件路径(如 "config.xml"),触发容器初始化。
  • 目标:创建 ClassPathXmlApplicationContext 实例,并完成父类初始化和配置路径设置。
2. 父类构造器逐层调用
ClassPathXmlApplicationContext->>AbstractXmlApplicationContext: super(parent)
AbstractXmlApplicationContext->>AbstractRefreshableConfigApplicationContext: super(parent)
AbstractRefreshableConfigApplicationContext->>AbstractRefreshableApplicationContext: super(parent)
AbstractRefreshableApplicationContext->>AbstractApplicationContext: super(parent)
  • 层级关系
    • ClassPathXmlApplicationContextAbstractXmlApplicationContextAbstractRefreshableConfigApplicationContextAbstractRefreshableApplicationContextAbstractApplicationContext
  • 核心逻辑
    • AbstractApplicationContext 中完成资源解析器和环境配置的初始化。
3. 资源解析器创建
AbstractApplicationContext->>PathMatchingResourcePatternResolver: new PathMatchingResourcePatternResolver(this)
  • 作用:创建 PathMatchingResourcePatternResolver 用于解析类路径下的资源(如 classpath:config.xml)。
  • 返回值:解析器实例返回给 AbstractApplicationContext,用于后续配置文件加载。
4. 环境配置合并(若有父容器)
alt parent != null
    AbstractApplicationContext->>parent: getEnvironment()
    ...(遍历父环境属性源、合并配置文件等)
end
  • 条件:若存在父容器(parent != null),当前容器会合并父容器的环境配置:
    • 属性源(PropertySources:将父环境中不存在的属性源按顺序添加到当前环境末尾(保证父配置优先级低于当前配置)。
    • 配置文件(Profiles):合并父容器的激活配置文件(Active Profiles)和默认配置文件(Default Profiles)。
二、setConfigLocations方法核心流程
1. 方法入口与参数校验
ClassPathXmlApplicationContext->>AbstractRefreshableConfigApplicationContext: setConfigLocations(configLocations)
AbstractRefreshableConfigApplicationContext->>AbstractRefreshableConfigApplicationContext: locations != null
AbstractRefreshableConfigApplicationContext->>AbstractRefreshableConfigApplicationContext: Assert.noNullElements(locations)
  • 参数校验
    • 检查 configLocations 是否为 null
    • 调用 Assert.noNullElements 确保数组中无 null 元素(避免空指针异常)。
2. 初始化配置路径数组
AbstractRefreshableConfigApplicationContext->>AbstractRefreshableConfigApplicationContext: this.configLocations = new String[locations.length]
  • 动作:根据传入的路径数组长度创建 configLocations 数组,用于存储解析后的路径。
3. 遍历每个配置路径并解析
loop 遍历每个location
    AbstractRefreshableConfigApplicationContext->>AbstractRefreshableConfigApplicationContext: resolvePath(locations[i])
  • 核心逻辑:对每个原始路径字符串 locations[i] 执行以下处理:
三、路径解析流程(resolvePath 方法)
1. 获取环境对象(ConfigurableEnvironment)
AbstractRefreshableConfigApplicationContext->>AbstractApplicationContext: getEnvironment()
  • 逻辑
    • 首次调用时,environmentnull,触发 createEnvironment() 创建 StandardEnvironment
    • StandardEnvironment 初始化时会添加系统属性(System.getProperties())和环境变量(System.getenv())到属性源。
2. 解析占位符(resolveRequiredPlaceholders)
AbstractRefreshableConfigApplicationContext->>ConfigurableEnvironment: resolveRequiredPlaceholders(path)
  • 调用链
    ConfigurableEnvironmentPropertySourcesPropertyResolverPropertyPlaceholderHelper
3. 占位符解析核心逻辑(`PropertyPlaceholderHelper`)
PropertyPlaceholderHelper->>PropertyPlaceholderHelper: parseStringValue(path, resolver, null)
  • 步骤分解
    1. 查找占位符起始位置:通过 indexOf("${") 定位占位符(如 ${app.config.path})。
    2. 查找占位符结束位置:调用 findPlaceholderEndIndex 确定 } 的位置。
    3. 提取占位符文本:如 app.config.path
    4. 递归解析嵌套占位符:若占位符中包含其他占位符(如 ${env.${type}.config}),递归调用 parseStringValue
    5. 解析属性值
      • 调用 resolver.resolvePlaceholder(placeholder) 从属性源中获取值。
      • 支持默认值(如 ${key:defaultValue}):若解析失败,使用冒号后的默认值。
    6. 替换占位符:将解析后的值替换到原始字符串中,循环处理直至无占位符。
4. 处理特殊情况
  • 循环引用:通过 visitedPlaceholders 集合检测循环引用(如 A=${B}, B=${A}),抛出 IllegalArgumentException
  • 未解析占位符:若 ignoreUnresolvablePlaceholdersfalse(默认),解析失败时抛出异常;否则保留原始字符串。
四、路径处理收尾
AbstractRefreshableConfigApplicationContext->>AbstractRefreshableConfigApplicationContext: resolvedPath.trim()
AbstractRefreshableConfigApplicationContext->>this.configLocations: [i] = resolvedPath
  • 动作
    • 去除解析后路径的前后空格(如 " config.xml ""config.xml")。
    • 将处理后的路径存入 configLocations 数组。
五、流程总结
关键类与职责
类名职责描述
ClassPathXmlApplicationContext入口类,负责初始化容器并设置配置路径。
AbstractRefreshableConfigApplicationContext存储配置路径并提供 setConfigLocations 方法。
ConfigurableEnvironment管理环境属性(如系统属性、环境变量),提供占位符解析能力。
PropertyPlaceholderHelper核心占位符解析器,处理嵌套占位符、默认值和循环引用。
数据流向
原始路径(客户端输入) → resolvePath → 解析占位符 → 去除空格 → 存入 configLocations 数组 → 后续用于加载配置文件
异常处理
  • 参数为空Assert.noNullElements 抛出 IllegalArgumentException
  • 占位符解析失败PropertyPlaceholderHelper 抛出 IllegalArgumentException
  • 循环引用:通过 visitedPlaceholders 检测并抛出异常。
六、与 Spring 容器启动的关联
  • 后续流程:构造器调用完成后,ClassPathXmlApplicationContext 会调用 refresh() 启动容器,此时 configLocations 中已存储解析后的路径,用于加载 XML 配置文件(如通过 PathMatchingResourcePatternResolver 加载资源)。
  • 核心作用setConfigLocations 是容器配置路径的“预处理”阶段,确保配置路径中的动态占位符(如从环境变量获取路径)在容器启动前完成解析。

通过以上流程,Spring 容器实现了配置路径的动态解析和校验,为后续 Bean 定义的加载和容器初始化奠定了基础。

启动源码入口
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
    this(configLocations, true, null);
}
时序图
Client ClassPathXmlApplicationContext AbstractXmlApplicationContext AbstractRefreshableConfigApplicationContext AbstractRefreshableApplicationContext AbstractApplicationContext PathMatchingResourcePatternResolver ConfigurableEnvironment PropertySources PropertySourcesPropertyResolver PropertyPlaceholderHelper parent activeProfiles defaultProfiles StandardEnvironment AbstractEnvironment propertySources resolver this.configLocations ClassPathXmlApplicationContext("config.xml") ClassPathXmlApplicationContext(["config.xml"], true, null) super(parent) super(parent) super(parent) super(parent) this() getResourcePatternResolver() new PathMatchingResourcePatternResolver(this) return resolver setParent(parent) getEnvironment() ConfigurableEnvironment getEnvironment() merge(parentEnvironment) getPropertySources() PropertySource[] contains(ps.getName()) addLast(ps) alt [!contains] loop [遍历父环境PropertySources] getActiveProfiles() String[] activeProfiles addAll(parentActiveProfiles) alt [!isEmpty(activeProfiles)] getDefaultProfiles() String[] defaultProfiles remove(RESERVED_DEFAULT_PROFILE_NAME) addAll(parentDefaultProfiles) alt [!isEmpty(defaultProfiles)] alt [parent != null] constructor returns constructor returns constructor returns setConfigLocations(configLocations) locations != null Assert.noNullElements(locations) this.configLocations = new String[locations.length] locations[i] resolvePath(locations[i]) getEnvironment() createEnvironment() new StandardEnvironment() super(propertySources) this.propertySources = propertySources createPropertyResolver(propertySources) new PropertySourcesPropertyResolver(propertySources) ConfigurablePropertyResolver customizePropertySources(propertySources) addLast(systemProperties) addLast(systemEnvironment) ConfigurableEnvironment return this.environment alt [environment == null] [environment != null] ConfigurableEnvironment resolveRequiredPlaceholders(path) resolveRequiredPlaceholders(path) strictHelper == null createPlaceholderHelper(false) new PropertyPlaceholderHelper("${", "}", ":", false) PropertyPlaceholderHelper doResolvePlaceholders(path, strictHelper) replacePlaceholders(path, resolver) parseStringValue(path, resolver, null) indexOf("${") findPlaceholderEndIndex(buf, startIndex) 提取placeholder文本 parseStringValue(placeholder, resolver, visited) resolvePlaceholder(placeholder) 分割placeholder和defaultValue resolvePlaceholder(actualPlaceholder) 使用defaultValue 使用解析值 alt [解析失败] [解析成功] resolvePlaceholder(placeholder) throw IllegalArgumentException 使用解析值 alt [解析失败] [解析成功] alt [placeholder有默认值(包含":")] [无默认值] parseStringValue(propVal, resolver, visited) replace(startIndex, endIndex, propVal) indexOf("${", startIndex + propVal.length()) loop [处理每个占位符] return result.toString() return path alt [找到占位符] [无占位符] 解析后的字符串 解析后的字符串 解析后的字符串 resolvedPath.trim() [i] = resolvedPath loop [遍历每个location] refresh() Client ClassPathXmlApplicationContext AbstractXmlApplicationContext AbstractRefreshableConfigApplicationContext AbstractRefreshableApplicationContext AbstractApplicationContext PathMatchingResourcePatternResolver ConfigurableEnvironment PropertySources PropertySourcesPropertyResolver PropertyPlaceholderHelper parent activeProfiles defaultProfiles StandardEnvironment AbstractEnvironment propertySources resolver this.configLocations
流程图
Client
└── 调用ClassPathXmlApplicationContext("config.xml")
    └── 执行ClassPathXmlApplicationContext(["config.xml", true, null])构造器
        ├── **调用父类构造器链**
        │   ├── ClassPathXmlApplicationContext->>AbstractXmlApplicationContext: super(parent)
        │   │   ├── AbstractXmlApplicationContext->>AbstractRefreshableConfigApplicationContext: super(parent)
        │   │   │   ├── AbstractRefreshableConfigApplicationContext->>AbstractRefreshableApplicationContext: super(parent)
        │   │   │   │   └── AbstractRefreshableApplicationContext->>AbstractApplicationContext: super(parent)
        │   │   │   │       ├── **AbstractApplicationContext初始化**
        │   │   │   │       │   ├── 调用this()无参构造器
        │   │   │   │       │   │   ├── 获取资源解析器:getReourcePatternResolver()
        │   │   │   │       │   │   │   └── 创建PathMatchingResourcePatternResolver实例
        │   │   │   │       │   │   └── 设置父容器:setParent(parent)
        │   │   │   │       │   │       └── **若父容器存在(parent != null)**
        │   │   │   │       │   │           ├── 合并环境配置(Environment)
        │   │   │   │       │   │           │   ├── 从父容器获取环境:parent.getEnvironment()
        │   │   │   │       │   │           │   ├── 当前容器获取/创建环境:getEnvironment()
        │   │   │   │       │   │           │   ├── 合并父环境的PropertySources(遍历添加不存在的属性源)
        │   │   │   │       │   │           │   ├── 合并激活配置文件(Active Profiles)
        │   │   │   │       │   │           │   └── 合并默认配置文件(Default Profiles)
        │   │   │   │       │   │       └── 父类构造器逐层返回
        │   │   │   │       └── 返回至AbstractRefreshableConfigApplicationContext
        │   │   └── 返回至AbstractXmlApplicationContext
        │   └── 返回至ClassPathXmlApplicationContext构造器
        ├── **设置配置路径**
        │   └── ClassPathXmlApplicationContext->>AbstractRefreshableConfigApplicationContext: setConfigLocations(configLocations)
        │       ├── 校验参数:locations != null 且无null元素
        │       ├── 初始化configLocations数组
        │       └── **遍历每个配置路径**
        │           └── 对每个location执行:
        │               ├── 调用resolvePath(locations[i])解析路径
        │               │   ├── 获取环境对象:getEnvironment()
        │               │   │   ├── **若环境未初始化(environment == null)**
        │               │   │   │   ├── 创建StandardEnvironment实例
        │               │   │   │   │   ├── 调用AbstractEnvironment构造器初始化属性源和解析器
        │               │   │   │   │   │   ├── 添加系统属性(systemProperties)和环境变量(systemEnvironment)到属性源
        │               │   │   │   │   └── 返回环境对象
        │               │   │   └── **若环境已初始化**:直接返回
        │               │   ├── 调用环境解析占位符:resolveRequiredPlaceholders(path)
        │               │   │   ├── PropertySourcesPropertyResolver创建PropertyPlaceholderHelper
        │               │   │   ├── 递归解析占位符(支持嵌套和默认值)
        │               │   │   ├── 处理循环引用和解析失败异常
        │               │   │   └── 返回解析后的字符串
        │               ├── 去除路径前后空格:resolvedPath.trim()
        │               └── 存入configLocations数组
        └── **启动容器刷新**

结尾

  • refresh核心源码在下一章进行解析,因为此方法属于公共方法,AnnotationConfigApplicationContext内部也是用此类的进行容器刷新,以及后面的Springmvc同样也是,所以说refresh源码重中之重。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值