Spring 源码解析:prepareRefresh() 方法深度剖析与面试指南

151 阅读3分钟

Spring 源码解析:prepareRefresh() 方法深度剖析与面试指南


引言

在 Spring 框架中,AbstractApplicationContext#refresh() 是容器初始化的核心入口方法,而 prepareRefresh() 是其第一个关键步骤。该方法负责容器的启动前准备工作,包括环境初始化、状态校验、资源预加载等。本文将通过源码逐行分析 prepareRefresh(),并总结高频面试考点。


一、prepareRefresh() 方法的作用

prepareRefresh() 是 Spring 容器启动流程的初始化前哨,主要完成以下任务:

  1. 记录容器启动时间戳。
  2. 设置容器的活跃/关闭状态标识。
  3. 初始化环境配置(Environment)。
  4. 校验环境中的必要参数。
  5. 初始化早期事件(Early Application Events)存储队列。

二、源码逐行解析

1. 方法签名与入口
protected void prepareRefresh() {
    this.startupDate = System.currentTimeMillis(); // 记录启动时间
    this.closed.set(false); // 标记容器未关闭
    this.active.set(true);  // 标记容器已激活

    // 初始化日志(根据环境配置)
    if (logger.isInfoEnabled()) {
        logger.info("Refreshing " + this);
    }

    // 初始化占位符属性源(如PropertySources)
    initPropertySources();

    // 验证环境中的必要属性
    getEnvironment().validateRequiredProperties();

    // 初始化早期事件监听器集合
    this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
    this.earlyApplicationEvents = new LinkedHashSet<>();
}
2. 关键操作详解
  • startupDate 记录
    标记容器启动时间,用于后续性能监控或生命周期追踪。

  • 状态标识 (closed/active)
    通过原子布尔变量保证多线程环境下的状态安全,避免容器重复启动或关闭。

  • initPropertySources()
    核心作用:初始化或扩展环境属性源(如系统变量、JNDI、配置文件等)。
    扩展点:子类(如 WebApplicationContext)可重写此方法,添加自定义属性源。
    例如,在 Web 环境中会添加 ServletContextServletConfig 属性源。

  • validateRequiredProperties()
    校验环境中必须存在的属性。若缺失,直接抛出 MissingRequiredPropertiesException
    例如,通过 @Value("${required.prop}") 注入的属性必须在此处声明。

  • 早期事件队列初始化
    在事件广播器(ApplicationEventMulticaster)初始化前,临时存储早期事件(如 ContextRefreshedEvent),待广播器就绪后发布。


三、高频面试考点与答案

1. prepareRefresh() 在容器启动流程中的角色是什么?
  • 答案:它是容器启动的第一个步骤,负责初始化环境、校验配置、准备事件队列,为后续 Bean 加载和依赖注入奠定基础。
2. initPropertySources() 的设计意义是什么?
  • 答案:这是一个典型的模板方法模式应用。父类定义算法骨架,子类(如 AnnotationConfigWebApplicationContext)通过重写该方法扩展属性源,实现环境定制化。
3. 如何强制要求某个属性必须配置?
  • 答案:在环境(Environment)中调用 setRequiredProperties("key"),若属性缺失,validateRequiredProperties() 将抛出异常。
4. 早期事件队列的作用是什么?
  • 答案:在事件广播器初始化前,临时存储监听器注册和事件发布,避免事件丢失。广播器就绪后,立即处理这些事件。
5. 如何扩展 Spring 的环境属性源?
  • 答案:通过重写 initPropertySources() 方法,调用 getEnvironment().getPropertySources().addLast() 添加自定义属性源(如数据库配置)。

四、总结与扩展

  • 设计模式prepareRefresh() 体现了模板方法模式(refresh() 方法流程固定,子类可扩展具体步骤)。
  • 性能优化:通过 earlyApplicationEvents 避免事件阻塞,提升容器启动效率。
  • 安全校验validateRequiredProperties() 是 Spring Boot 的 @ConditionalOnProperty 等条件注解的底层依赖。

附:典型面试题

Q: 如果在 prepareRefresh() 阶段发现必要属性缺失,Spring 会如何处理?
A: 抛出 MissingRequiredPropertiesException,阻止容器继续启动。

Q: 如何在不修改源码的情况下扩展环境属性源?
A: 实现 ApplicationContextInitializer 接口,在 initialize() 方法中操作 ConfigurableEnvironmentPropertySources


通过深入理解 prepareRefresh(),开发者不仅能掌握 Spring 容器的启动机制,还能灵活应对环境配置、属性校验等实际需求。在面试中,结合源码和设计模式回答此类问题,将显著提升技术深度评价。