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