@ConditionalOnProperty配置生效问题java
在使用Nacos-Config-SpringBoot
时,有使用者反馈说没法支持@ConditionalOnProperty
注解,通过研究Nacos-Config-SpringBoot
、Nacos-Spring-Context
源码以及调试跟踪SpringBoot
的初始化流程,最终发现问题所在——@ConditionalOnProperty
的解析时间与Nacos-Spring-Context
相关Bean
的注册以及工做时间存在前后问题(其本质缘由就是Bean
的加载顺序)git
要想知道@ConditionalOnProperty
注解什么时候被Spring
解析,首先要看另一个类——ConfigurationClassPostProcessor
,这个类实现了BeanFactoryPostProcessor
接口,所以他能够在Spring
的bean
建立以前,对bean
进行修改,即Spring
容许BeanFactoryPostProcessor
在容器实例化任何其余bean
以前读取配置元数据,并根据其进行修改,就好比@ConditionalOnProperty
注解经常使用来根据配置文件的相关配置控制是否须要建立相应的bean
。github
核心代码spring
ConfigurationClassPostProcessor
执行、装载时机bootstrap
refreshContext(context);
@Override
protected final void refreshBeanFactory() throws BeansException {
...
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
复制代码
处理@Configuration
等注解的处理代码bash
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList();
String[] candidateNames = registry.getBeanDefinitionNames();
String[] var4 = candidateNames;
int var5 = candidateNames.length;
...
do {
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
...
}
复制代码
在代码parser.getConfigurationClasses()
执行后,会根据Environment
中的配置元数据以及@ConditionalOnProperty
中的设置,对bean
进行过滤操做,不满条件的bean
不会被转载进Spring Container
中app
people.enable=false时ide
people.enable=true时spring-boot
(提醒:之因此这里nacos的配置可以与SpringBoot
的@ConditionalOnProperty
,是由于我这进行了修改,初步解决了此问题)post
而Nacos-Spring-Context
中相关的bean
,都是在这以后才被解析、装载进入Spring Container
的,为何这么说?这里直接拉出官方对于ConfigurationClassPostProcessor
的注释
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
* {@link Configuration @Configuration} classes.
*
* <p>Registered by default when using {@code <context:annotation-config/>} or
* {@code <context:component-scan/>}. Otherwise, may be declared manually as
* with any other BeanFactoryPostProcessor.
*
* <p>This post processor is priority-ordered as it is important that any
* {@link Bean} methods declared in {@code @Configuration} classes have
* their corresponding bean definitions registered before any other
* {@link BeanFactoryPostProcessor} executes.
复制代码
大体意思就是,ConfigurationClassPostProcessor
的优先级是全部BeanFactoryPostProcessor
实现中最高的,他必须在其余的BeanFactoryPostProcessor
以前执行,由于其余的BeanFactoryPostProcessor
须要ConfigurationClassPostProcessor
进行解析装载进Spring Container
。
清楚该ISSUE为何会出现的缘由以后,那么相应的解决方案就很快的出来了。以前说到ConfigurationClassPostProcessor
它会去解析@ConditionalOnProperty
,而且它的执行先于其余的BeanFactoryPostProcessor
实现;那么,还有什么比它先执行呢?
ApplicationContextInitializer
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
复制代码
能够看出,ApplicationContextInitializer
是在prepareContext
执行的,而ConfigurationClassPostProcessor
是在refreshContext
执行的,所以,咱们只须要将配置拉取的代码提早到ApplicationContextInitializer
中便可
@Override
public void initialize(ConfigurableApplicationContext context) {
environment = context.getEnvironment();
if (isEnable()) {
CompositePropertySource compositePropertySource = new CompositePropertySource(NacosConfigConstants.NACOS_BOOTSTRAP_PROPERTY_APPLICATION);
CacheableEventPublishingNacosServiceFactory singleton = CacheableEventPublishingNacosServiceFactory.getSingleton();
singleton.setApplicationContext(context);
String[] dataIds;
String[] groupIds;
String[] namespaces;
try {
dataIds = environment.getProperty(NacosConfigConstants.NACOS_CONFIG_DATA_ID, String[].class, new String[]{});
groupIds = environment.getProperty(NacosConfigConstants.NACOS_CONFIG_GROUP_ID, String[].class, new String[dataIds.length]);
namespaces = environment.getProperty(NacosProperties.NAMESPACE, String[].class, new String[dataIds.length]);
for (int i = 0; i < dataIds.length; i ++) {
Properties buildInfo = properties(namespaces[i]);
ConfigService configService = singleton.createConfigService(buildInfo);
String group = StringUtils.isEmpty(groupIds[i]) ? Constants.DEFAULT_GROUP : groupIds[i];
String config = configService.getConfig(dataIds[i], group, 1000);
if (config == null) {
logger.error("nacos-config-spring-boot : get config failed");
continue;
}
String name = buildDefaultPropertySourceName(dataIds[i], groupIds[i], buildInfo);
NacosPropertySource nacosPropertySource = new NacosPropertySource(name, config);
compositePropertySource.addPropertySource(nacosPropertySource);
}
environment.getPropertySources().addFirst(compositePropertySource);
} catch (NacosException e) {
logger.error(e.getErrMsg());
}
}
}
复制代码