记:一次 spring cloud @PostConstruct 执行两次的分析

版权声明:本文为博主原创文章,转载请附上博文链接,谢谢! https://blog.csdn.net/qq_30062125/article/details/84251630

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容器初始化中的一个步骤,具体如下:

/**
 * 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使用在同一个类中,所以导致bean初始化了两次。

下面简单看下逻辑
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;
}

猜你喜欢

转载自blog.csdn.net/qq_30062125/article/details/84251630