記一次解決Nacos-SpringBoot不支持@ConditionalOnProperty

錯誤介紹

@ConditionalOnProperty配置生效問題java

錯誤緣由分析

在使用Nacos-Config-SpringBoot時,有使用者反饋說沒法支持@ConditionalOnProperty註解,通過研究Nacos-Config-SpringBootNacos-Spring-Context源碼以及調試跟蹤SpringBoot的初始化流程,最終發現問題所在——@ConditionalOnProperty的解析時間與Nacos-Spring-Context相關Bean的註冊以及工做時間存在前後問題(其本質緣由就是Bean的加載順序)git

@ConditionalOnProperty解析時間

要想知道@ConditionalOnProperty註解什麼時候被Spring解析,首先要看另一個類——ConfigurationClassPostProcessor,這個類實現了BeanFactoryPostProcessor接口,所以他能夠在Springbean建立以前,對bean進行修改,即Spring容許BeanFactoryPostProcessor在容器實例化任何其餘bean以前讀取配置元數據,並根據其進行修改,就好比@ConditionalOnProperty註解經常使用來根據配置文件的相關配置控制是否須要建立相應的beangithub

核心代碼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 Containerapp

測試代碼

people.enable=false時ide

nacos-server 配置信息-1

代碼執行狀況-false

people.enable=true時spring-boot

nacos-server 配置信息-2

代碼執行狀況-true

(提醒:之因此這裏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());
        }
    }
}
複製代碼
相關文章
相關標籤/搜索