Error is introduced
ConditionalOnProperty configuration effectiveness issue
Error cause analysis
The @conditionalonproperty annotation is not supported when using nacos-config-Springboot. After studying the source code of nacos-config-Springboot, nacos-spring-Context and debugging and tracking the initialization process of SpringBoot, Finally found the problem – @conditionalonProperty parsing time and nacos-spring-context related Bean registration and working time issue (the essence of the reason is the Bean loading order)
ConditionalOnProperty Parsing time
If you want to know when @ ConditionalOnProperty annotation by the Spring, the first thing to look at another class — ConfigurationClassPostProcessor, this class implements a Spring BeanFactoryPostProcessor interface, Spring allows BeanFactoryPostProcessor to read configuration metadata and modify it based on it before the container instantiates any other beans. The @conditionalonProperty annotation, for example, is used to control whether a bean needs to be created based on the configuration of the configuration file.
The core code
ConfigurationClassPostProcessor execution, loading time
refreshContext(context);
@Override
protected final void refreshBeanFactory(a) 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); }}Copy the code
Handling code for @Configuration and other annotations
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList();
String[] candidateNames = registry.getBeanDefinitionNames();
String[] var4 = candidateNames;
intvar5 = 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()); }... }Copy the code
After the code parser. GetConfigurationClasses (), depending on the configuration of the Environment in the metadata, and @ ConditionalOnProperty Settings, to filter operation of bean, Beans that are not conditionally loaded are not loaded into the Spring Container
When people. Enable = false
People. The enable = true
(Note: the reason why the configuration of nacOS here can match SpringBoot’s @conditionalonProperty is that I have modified it and solved the problem preliminatively.)
The nacos-spring-context beans are parsed and loaded into the Spring Container after that. Directly out of the official for ConfigurationClassPostProcessor comments here
* {@link BeanFactoryPostProcessor} used forbootstrapping 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 declaredin {@code @Configuration} classes have * their corresponding bean definitions registered before any other * {@link BeanFactoryPostProcessor} executes. Copy the code
Which means, roughly ConfigurationClassPostProcessor priority is the highest of all spring BeanFactoryPostProcessor implementation, he must be performed before the other spring BeanFactoryPostProcessor, Because other Spring BeanFactoryPostProcessor need ConfigurationClassPostProcessor parse loading into the Spring Container.
The solution
Once the reason for the ISSUE is clear, a solution can be found quickly. Said before to ConfigurationClassPostProcessor it will parse @ ConditionalOnProperty, and its execution before other spring BeanFactoryPostProcessor implementation; So, what comes before it?
ApplicationContextInitializer
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
Copy the code
It can be seen that ApplicationContextInitializer prepareContext execution, and ConfigurationClassPostProcessor refreshContext execution, as a result, We just need to configure the pull code into ApplicationContextInitializer can ahead of time
@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()); }}}Copy the code