1、前言
最近遇到一个情况,@PostConstruct 方法会被执行两次,感觉有点奇怪,跟踪代码简单分析了下,场景有点特殊,这里记录下。
@PostConstruct属于JSR250规范,在bean创建完成并且属性赋值完成之后会执行该初始化方法。
内部通过InitDestroyAnnotationBeanPostProcessor实现逻辑。
2、@PostConstruct 出问题写法说明
一般使用方法比较简单,bean中添加一个注解方法即可。
@Component
public class Demo {
@PostConstruct
public void init(){
System.out.println("bean....@PostConstruct...");
}
}
出问题的写法有点特别,多了@ConfigurationProperties注解,类似:
@Component
@ConfigurationProperties(prefix = "test")
public class Demo {
private String test;
@PostConstruct
public void init(){
System.out.println("bean....@PostConstruct...");
}
}
3、修复方法
修复方法也比较简单,将@ConfigurationProperties逻辑拆到单独的一个类中,将@PostConstruct放在另外一个类中即可。类似:
// 属性类
@Component
@ConfigurationProperties(prefix = "test")
public class TestConfig {
private String test;
}
// 逻辑类
@Component
public class Demo {
@Autowired
TestConfig testConfig;
@PostConstruct
public void init(){
System.out.println("bean....@PostConstruct...");
}
}
4、问题分析
跟踪逻辑之后发现,第一次@PostConstruct的执行很正常,就是普通的bean创建过程。
第二次执行,我们会发现触发逻辑在
org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization中的beanFactory.preInstantiateSingletons();
就是spring容器初始化中的一个步骤,该逻辑包含了@PostConstruct步骤。
具体如下:
/**
* Finish the initialization of this context's bean factory,
* initializing all remaining singleton beans.
*/
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有剩下的单实例bean。
// 所有Bean都getBean创建完成以后,会检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated();
beanFactory.preInstantiateSingletons();
}
这里发现有个bean叫做ConfigurationPropertiesRebinderAutoConfiguration。这是导致@PostConstruct 执行两次的根本原因。
这个bean主要是监听EnvironmentChangeEvent事件用于刷新@ConfigurationProperties标记的配置
从这可以看出,是因为@ConfigurationProperties和@PostConstruct使用在同一个类中导致的。
第一次@Component正常创建bean执行了一次@PostConstruct,
第二次@ConfigurationProperties又触发了一次bean初始化过程,包含@PostConstruct,所以导致@PostConstruct执行了两次。下面简单看下逻辑
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration#afterSingletonsInstantiated 发布环境变更事件
@Override
public void afterSingletonsInstantiated() {
// After all beans are initialized send a pre-emptive EnvironmentChangeEvent
// so that anything that needs to rebind gets a chance now (especially for
// beans in the parent context)
this.context.publishEvent(
new EnvironmentChangeEvent(Collections.<String> emptySet()));
}
org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder 监听了这个事件
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
rebind();
}
org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind() 所有的ConfigurationPropertiesBeans,触发initializeBean(bean, name) 初始化bean。和普通的bean初始化类似。粗略分为四步:
1)、【执行Aware接口方法】
2)、【执行后置处理器postProcessBeforeInitialization方法
3)、【执行初始化Initializing方法】
4)、【执行后置处理器postProcessAfterInitialization方法】\
@ManagedOperation
public void rebind() {
this.errors.clear();
//触发所有ConfigurationPropertiesBeans的初始化
for (String name : this.beans.getBeanNames()) {
rebind(name);
}
}
@ManagedOperation
public boolean rebind(String name) {
if (!this.beans.getBeanNames().contains(name)) {
return false;
}
if (this.applicationContext != null) {
try {
Object bean = this.applicationContext.getBean(name);
if (AopUtils.isCglibProxy(bean)) {
bean = getTargetObject(bean);
}
this.binder.postProcessBeforeInitialization(bean, name);
// 初始化
this.applicationContext.getAutowireCapableBeanFactory()
.initializeBean(bean, name);
return true;
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
return false;
}
其中InitDestroyAnnotationBeanPostProcessor就是一个后置处理器,@PostConstruct逻辑在第二步触发执行。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}
ConfigurationPropertiesBindingPostProcessor也是一个后置处理器,@ConfigurationProperties逻辑还是在第二步触发执行。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
ConfigurationProperties annotation = AnnotationUtils
.findAnnotation(bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
annotation = this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation);
}
return bean;
}