【附录】Spring容器启动流程详解 - prepareRefresh()方法分析

此文是 【Spring 容器详解】-> 【【附录】Spring容器的启动过程】的支节点。

在Spring容器的启动过程中,AbstractApplicationContext.refresh()方法是整个容器初始化的核心入口。而prepareRefresh()作为这个方法的第一个步骤,承担着容器启动前的准备工作,为后续的Bean创建和管理奠定基础。

1. prepareRefresh()方法在refresh()流程中的位置

java 复制代码
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新 - 本文重点分析的方法
        prepareRefresh();
        
        // 2. 获取BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3. 准备BeanFactory
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 允许子类在标准初始化后修改BeanFactory
            postProcessBeanFactory(beanFactory);
            
            // 5. 调用BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 6. 注册BeanPostProcessor
            registerBeanPostProcessors(beanFactory);
            
            // 7. 初始化消息源
            initMessageSource();
            
            // 8. 初始化事件广播器
            initApplicationEventMulticaster();
            
            // 9. 初始化特定上下文子类中的其他特殊bean
            onRefresh();
            
            // 10. 注册监听器
            registerListeners();
            
            // 11. 实例化所有非懒加载的单例Bean
            finishBeanFactoryInitialization(beanFactory);
            
            // 12. 完成刷新
            finishRefresh();
        } catch (BeansException ex) {
            // 清理资源
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        } finally {
            // 重置Spring通用的内省缓存
            resetCommonCaches();
        }
    }
}

2. prepareRefresh()方法源码分析

2.1 方法签名和基本结构

java 复制代码
protected void prepareRefresh() {
    // 设置启动时间
    this.startupDate = System.currentTimeMillis();
    
    // 设置容器状态标志
    this.closed.set(false);
    this.active.set(true);
    
    if (logger.isDebugEnabled()) {
        logger.debug("Refreshing " + this);
    }
    
    // 初始化属性源
    initPropertySources();
    
    // 验证必需的属性
    getEnvironment().validateRequiredProperties();
    
    // 存储早期ApplicationListeners
    this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}

2.2 详细步骤分析

步骤1: 设置启动时间

java 复制代码
// 记录容器启动的时间戳
this.startupDate = System.currentTimeMillis();

作用:

  • 记录容器的启动时间,用于性能监控和调试
  • 为后续的时间相关操作提供基准时间
  • 支持容器的生命周期管理

步骤2: 设置容器状态标志

java 复制代码
// 设置容器为未关闭状态
this.closed.set(false);
// 设置容器为活跃状态
this.active.set(true);

作用:

  • closed标志:标识容器是否已关闭,防止在关闭状态下执行操作
  • active标志:标识容器是否处于活跃状态,用于状态检查
  • 这些标志是线程安全的,使用AtomicBoolean实现

步骤3: 初始化属性源

java 复制代码
// 初始化属性源,允许子类重写以添加自定义属性源
initPropertySources();

initPropertySources()方法的默认实现:

java 复制代码
protected void initPropertySources() {
    // 默认实现为空,允许子类重写
    // 子类可以在这里添加自定义的属性源
}

子类重写示例(WebApplicationContext):

java 复制代码
@Override
protected void initPropertySources() {
    // Web应用特有的属性源初始化
    ConfigurableEnvironment env = getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
}

步骤4: 验证必需的属性

java 复制代码
// 验证环境中的必需属性
getEnvironment().validateRequiredProperties();

validateRequiredProperties()方法的作用:

java 复制代码
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
    MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
    
    for (String key : this.requiredProperties) {
        if (getProperty(key) == null) {
            ex.addMissingRequiredProperty(key);
        }
    }
    
    if (!ex.getMissingRequiredProperties().isEmpty()) {
        throw ex;
    }
}

必需属性的配置方式:

java 复制代码
// 在配置类中设置必需属性
@Configuration
public class AppConfig {
    
    @PostConstruct
    public void init() {
        // 设置必需属性
        Environment env = applicationContext.getEnvironment();
        if (env instanceof ConfigurableEnvironment) {
            ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
            configEnv.setRequiredProperties("database.url", "database.username");
        }
    }
}

步骤5: 准备早期事件监听器

java 复制代码
// 存储早期ApplicationListeners,用于处理早期事件
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);

早期事件监听器的作用:

  • 在容器完全初始化之前就能处理某些事件
  • 支持在Bean创建过程中发布和监听事件
  • 为容器的启动过程提供事件支持

3. prepareRefresh()方法的扩展点

3.1 子类可以重写的方法

3.1.1 initPropertySources() - 初始化属性源

java 复制代码
// 自定义属性源示例
@Configuration
public class CustomApplicationContext extends AnnotationConfigApplicationContext {
    
    @Override
    protected void initPropertySources() {
        // 添加自定义属性源
        ConfigurableEnvironment env = getEnvironment();
        MutablePropertySources propertySources = env.getPropertySources();
        
        // 添加系统属性源
        propertySources.addFirst(new SystemEnvironmentPropertySource("systemEnvironment", System.getenv()));
        
        // 添加自定义属性源
        Properties customProps = new Properties();
        customProps.setProperty("custom.key", "custom.value");
        propertySources.addLast(new PropertiesPropertySource("customProperties", customProps));
    }
}

3.1.2 环境配置扩展

java 复制代码
// 环境配置扩展示例
@Configuration
public class EnvironmentConfig {
    
    @Bean
    public Environment environment() {
        ConfigurableEnvironment env = new StandardEnvironment();
        
        // 设置默认配置文件
        env.setDefaultProfiles("default");
        
        // 设置活跃配置文件
        env.setActiveProfiles("dev", "local");
        
        // 添加自定义属性源
        MutablePropertySources propertySources = env.getPropertySources();
        propertySources.addFirst(new ResourcePropertySource("classpath:application.properties"));
        
        return env;
    }
}

3.2 属性验证的配置

3.2.1 必需属性配置

java 复制代码
// 在application.properties中配置
database.url=${DB_URL:jdbc:mysql://localhost:3306/test}
database.username=${DB_USERNAME:root}
database.password=${DB_PASSWORD:password}

# 在配置类中设置必需属性
@Configuration
public class DatabaseConfig {
    
    @PostConstruct
    public void validateProperties(Environment env) {
        if (env instanceof ConfigurableEnvironment) {
            ConfigurableEnvironment configEnv = (ConfigurableEnvironment) env;
            configEnv.setRequiredProperties("database.url", "database.username", "database.password");
        }
    }
}

3.2.2 属性验证器

java 复制代码
// 自定义属性验证器
@Component
public class CustomPropertyValidator implements EnvironmentPostProcessor {
    
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // 验证数据库连接属性
        String dbUrl = environment.getProperty("database.url");
        if (dbUrl == null || !dbUrl.startsWith("jdbc:")) {
            throw new IllegalStateException("Invalid database URL configuration");
        }
        
        // 验证端口号
        String port = environment.getProperty("server.port");
        if (port != null) {
            try {
                int portNum = Integer.parseInt(port);
                if (portNum < 1 || portNum > 65535) {
                    throw new IllegalStateException("Invalid port number: " + portNum);
                }
            } catch (NumberFormatException e) {
                throw new IllegalStateException("Invalid port number: " + port);
            }
        }
    }
}

4. prepareRefresh()方法的实际应用场景

4.1 性能监控和调试

java 复制代码
// 性能监控示例
@Component
public class StartupPerformanceMonitor {
    
    private long startupTime;
    
    @EventListener
    public void handleContextRefreshed(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        if (context instanceof AbstractApplicationContext) {
            AbstractApplicationContext abstractContext = (AbstractApplicationContext) context;
            long startupDate = abstractContext.getStartupDate();
            long currentTime = System.currentTimeMillis();
            long duration = currentTime - startupDate;
            
            System.out.println("Spring容器启动耗时: " + duration + "ms");
        }
    }
}

4.2 环境配置管理

java 复制代码
// 环境配置管理示例
@Configuration
public class EnvironmentConfig {
    
    @Bean
    public EnvironmentPostProcessor environmentPostProcessor() {
        return (environment, application) -> {
            // 根据环境设置不同的配置
            String[] activeProfiles = environment.getActiveProfiles();
            if (activeProfiles.length == 0) {
                environment.setDefaultProfiles("default");
            }
            
            // 添加环境特定的属性源
            for (String profile : activeProfiles) {
                String configFile = "classpath:application-" + profile + ".properties";
                try {
                    environment.getPropertySources().addLast(
                        new ResourcePropertySource(configFile)
                    );
                } catch (IOException e) {
                    // 配置文件不存在时忽略
                }
            }
        };
    }
}

4.3 早期事件处理

java 复制代码
// 早期事件监听器示例
@Component
public class EarlyEventListener implements ApplicationListener<ApplicationEvent> {
    
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationContextEvent) {
            ApplicationContext context = ((ApplicationContextEvent) event).getApplicationContext();
            
            // 在容器完全初始化之前处理事件
            if (event instanceof ContextRefreshedEvent) {
                System.out.println("容器刷新事件触发");
            } else if (event instanceof ContextStartedEvent) {
                System.out.println("容器启动事件触发");
            }
        }
    }
}

5. prepareRefresh()方法的异常处理

5.1 常见异常类型

5.1.1 MissingRequiredPropertiesException

java 复制代码
// 必需属性缺失异常
try {
    context.refresh();
} catch (MissingRequiredPropertiesException e) {
    System.err.println("缺少必需的配置属性:");
    for (String property : e.getMissingRequiredProperties()) {
        System.err.println("  - " + property);
    }
    // 处理异常
}

5.1.2 BeanCreationException

java 复制代码
// Bean创建异常
try {
    context.refresh();
} catch (BeanCreationException e) {
    System.err.println("Bean创建失败: " + e.getBeanName());
    System.err.println("原因: " + e.getMessage());
    // 处理异常
}

5.2 异常处理策略

java 复制代码
// 异常处理策略示例
@Configuration
public class ExceptionHandlingConfig {
    
    @Bean
    public ApplicationListener<ApplicationEvent> exceptionHandler() {
        return event -> {
            if (event instanceof ApplicationContextEvent) {
                ApplicationContext context = ((ApplicationContextEvent) event).getApplicationContext();
                
                // 检查容器状态
                if (context instanceof ConfigurableApplicationContext) {
                    ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) context;
                    if (configContext.isActive()) {
                        System.out.println("容器启动成功");
                    } else {
                        System.err.println("容器启动失败");
                    }
                }
            }
        };
    }
}

6. prepareRefresh()方法的性能优化

6.1 启动性能优化

java 复制代码
// 启动性能优化示例
@Configuration
public class PerformanceConfig {
    
    @Bean
    public ApplicationListener<ContextRefreshedEvent> performanceMonitor() {
        return event -> {
            ApplicationContext context = event.getApplicationContext();
            if (context instanceof AbstractApplicationContext) {
                AbstractApplicationContext abstractContext = (AbstractApplicationContext) context;
                long startupTime = abstractContext.getStartupDate();
                long currentTime = System.currentTimeMillis();
                long duration = currentTime - startupTime;
                
                // 记录启动时间
                System.out.println("容器启动耗时: " + duration + "ms");
                
                // 如果启动时间过长,记录警告
                if (duration > 5000) {
                    System.warn("容器启动时间过长,建议优化配置");
                }
            }
        };
    }
}

6.2 属性源优化

java 复制代码
// 属性源优化示例
@Configuration
public class PropertySourceOptimization {
    
    @Bean
    public EnvironmentPostProcessor propertySourceOptimizer() {
        return (environment, application) -> {
            MutablePropertySources propertySources = environment.getPropertySources();
            
            // 优化属性源顺序,将常用的放在前面
            if (propertySources.contains("systemProperties")) {
                PropertySource<?> systemProps = propertySources.get("systemProperties");
                propertySources.remove("systemProperties");
                propertySources.addFirst(systemProps);
            }
            
            // 添加缓存属性源
            propertySources.addLast(new CachedPropertySource("cachedProperties"));
        };
    }
}

// 缓存属性源实现
class CachedPropertySource extends PropertySource<Properties> {
    
    private final Properties cachedProperties = new Properties();
    
    public CachedPropertySource(String name) {
        super(name, new Properties());
    }
    
    @Override
    public Object getProperty(String name) {
        return cachedProperties.getProperty(name);
    }
}

总结

prepareRefresh()方法的核心作用

  1. 状态管理:设置容器的启动状态和标志位
  2. 时间记录:记录容器启动时间,用于性能监控
  3. 属性初始化:初始化属性源,支持自定义属性配置
  4. 属性验证:验证必需的配置属性,确保配置完整性
  5. 事件准备:准备早期事件监听器,支持事件机制

方法的重要性

  1. 启动入口:是容器启动流程的第一个步骤,为后续操作奠定基础
  2. 扩展点:提供了多个可重写的方法,支持自定义行为
  3. 错误预防:通过属性验证,提前发现配置问题
  4. 性能基础:为性能监控和优化提供基础数据

最佳实践建议

  1. 合理配置必需属性:只将真正必需的属性标记为必需
  2. 优化属性源顺序:将常用的属性源放在前面,提高访问效率
  3. 实现自定义属性源:根据业务需求实现特定的属性源
  4. 监控启动性能:利用启动时间数据进行性能优化
  5. 异常处理:妥善处理配置相关的异常,提供友好的错误信息

prepareRefresh()方法虽然代码量不多,但它是Spring容器启动流程的重要基础,理解其工作原理对于深入理解Spring框架和优化应用启动性能具有重要意义。

相关推荐
xuejianxinokok14 分钟前
解惑rust中的 Send/Sync(译)
后端·rust
Siler24 分钟前
Oracle利用数据泵进行数据迁移
后端
用户67570498850235 分钟前
3分钟,手摸手教你用OpenResty搭建高性能隧道代理(附完整配置!)
后端
coding随想1 小时前
网络世界的“快递站”:深入浅出OSI七层模型
后端·网络协议
skeletron20111 小时前
🚀AI评测这么玩(2)——使用开源评测引擎eval-engine实现问答相似度评估
前端·后端
shark_chili1 小时前
颠覆认知!这才是synchronized最硬核的打开方式
后端
就是帅我不改1 小时前
99%的Java程序员都写错了!高并发下你的Service层正在拖垮整个系统!
后端·架构
Apifox1 小时前
API 文档中有多种参数结构怎么办?Apifox 里用 oneOf/anyOf/allOf 这样写
前端·后端·测试
似水流年流不尽思念1 小时前
如何实现一个线程安全的单例模式?
后端·面试
楽码1 小时前
了解HMAC及实现步骤
后端·算法·微服务