此文是 【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()方法的核心作用
- 状态管理:设置容器的启动状态和标志位
- 时间记录:记录容器启动时间,用于性能监控
- 属性初始化:初始化属性源,支持自定义属性配置
- 属性验证:验证必需的配置属性,确保配置完整性
- 事件准备:准备早期事件监听器,支持事件机制
方法的重要性
- 启动入口:是容器启动流程的第一个步骤,为后续操作奠定基础
- 扩展点:提供了多个可重写的方法,支持自定义行为
- 错误预防:通过属性验证,提前发现配置问题
- 性能基础:为性能监控和优化提供基础数据
最佳实践建议
- 合理配置必需属性:只将真正必需的属性标记为必需
- 优化属性源顺序:将常用的属性源放在前面,提高访问效率
- 实现自定义属性源:根据业务需求实现特定的属性源
- 监控启动性能:利用启动时间数据进行性能优化
- 异常处理:妥善处理配置相关的异常,提供友好的错误信息
prepareRefresh()
方法虽然代码量不多,但它是Spring容器启动流程的重要基础,理解其工作原理对于深入理解Spring框架和优化应用启动性能具有重要意义。