springcloud情操陶冶-bootstrapContext(二)

承接前文監聽器對bootstrapContext建立的引導,筆者瞭解到其主要入口類爲BootstrapImportSelectorConfiguration。本文將基於此類進行簡單的分析java

BootstrapImportSelectorConfiguration

簡單的配置類,看下源碼spring

@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {
}

嗯,引入了延遲加載類BootstrapImportSelector,那筆者就繼續往下看下此會延遲加載哪些類,直接去觀察其主方法bootstrap

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        List<String> names = new ArrayList<>(SpringFactoriesLoader
                .loadFactoryNames(BootstrapConfiguration.class, classLoader));
        names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
                environment.getProperty("spring.cloud.bootstrap.sources", ""))));

        List<OrderedAnnotatedElement> elements = new ArrayList<>();
        for (String name : names) {
            try {
                elements.add(new OrderedAnnotatedElement(metadataReaderFactory, name));
            } catch (IOException e) {
                continue;
            }
        }
        AnnotationAwareOrderComparator.sort(elements);

        String[] classNames = elements.stream()
                .map(e -> e.name)
                .toArray(String[]::new);

        return classNames;
    }

上述的代碼很簡單,其會去加載classpath路徑下全部spring.factories文件中以org.springframework.cloud.bootstrap.BootstrapConfiguration做爲Key的全部類;
同時springcloud也支持經過設置spring.cloud.bootstrap.sources屬性來加載指定類api

筆者就先以springcloud context板塊內的spring.factories做爲分析的源頭緩存

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

在分析上述的源碼以前,筆者必須得清楚如今bootstrapContext加載的配置文件默認爲bootstrap.properties抑或是bootstrap.yml,其屬性已經被加入至相應的Environment對象中。基於此咱們再往下走,以避免犯糊塗安全

PropertySourceBootstrapConfiguration

配置源的加載,此Configuration主要用於肯定是否外部加載的配置屬性複寫Spring內含的環境變量。注意其是ApplicationContextInitializer接口的實現類,前文已經提到,bootstrapContext上的此接口的bean類都會被註冊至子級的SpringApplication對象上。
直接看下主要的代碼片斷把app

@Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        CompositePropertySource composite = new CompositePropertySource(
                BOOTSTRAP_PROPERTY_SOURCE_NAME);
        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
        boolean empty = true;
        // 此處爲子級的環境變量對象
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        // 經過PropertySourceLocator接口去加載外部配置
        for (PropertySourceLocator locator : this.propertySourceLocators) {
            PropertySource<?> source = null;
            source = locator.locate(environment);
            if (source == null) {
                continue;
            }
            logger.info("Located property source: " + source);
            composite.addPropertySource(source);
            empty = false;
        }
        if (!empty) {
            MutablePropertySources propertySources = environment.getPropertySources();
            String logConfig = environment.resolvePlaceholders("${logging.config:}");
            LogFile logFile = LogFile.get(environment);
            if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
            }
            // 肯定屬性讀取的前後順序
            insertPropertySources(propertySources, composite);
            // reinitialize log
            reinitializeLoggingSystem(environment, logConfig, logFile);
            setLogLevels(applicationContext, environment);
            // active profiles process
            handleIncludedProfiles(environment);
        }
    }

上述的代碼就涉及兩點,一個是經過PropertySourceLocator接口加載外部配置;一個是用於解析以spring.cloud.config爲開頭的PropertySourceBootstrapProperties屬性,默認狀況下,外部配置比內部變量有更高的優先級。具體的用戶可自行分析ide

備註:PropertiesSourceBootstrapProperties中的屬性變量可經過PropertySourceLocator接口配置this

PropertyPlaceholderAutoConfiguration

和spring常見的解析文件同樣的操做,具體就不分析了spa

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

    // 配置文件屬性讀取經常使用類
    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

ConfigurationPropertiesRebinderAutoConfiguration

經過命名便會發現其跟刷新屬性的功能有關,先優先看下其類結構

@Configuration
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
public class ConfigurationPropertiesRebinderAutoConfiguration
        implements ApplicationContextAware, SmartInitializingSingleton {
}

上述代碼表示其依據於當前類環境存在ConfigurationPropertiesBindingPostProcessorBean對象纔會被應用,仔細查閱了下,發現只要有使用到@EnableConfigurationProperties註解即就會被註冊。看來此配置跟ConfigurationProperties註解也有必定的關聯性。

本文就羅列筆者比較關注的幾個地方


1.ConfigurationPropertiesRebinder對象的建立

@Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public ConfigurationPropertiesRebinder configurationPropertiesRebinder(
            ConfigurationPropertiesBeans beans) {
        ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(
                beans);
        return rebinder;
    }

此類讀者能夠自行翻閱代碼,能夠發現其暴露了JMX接口以及監聽了springcloud context自定義的EnvironmentChangeEvent事件。看來其主要用來刷新ApplicationContext上的beans(含@ConfigurationProperties註解)對象集合


2.ConfigurationPropertiesBeans對象的建立

@Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public ConfigurationPropertiesBeans configurationPropertiesBeans() {
        // 
        ConfigurationBeanFactoryMetadata metaData = this.context.getBean(
                ConfigurationBeanFactoryMetadata.BEAN_NAME,
                        ConfigurationBeanFactoryMetadata.class);
        ConfigurationPropertiesBeans beans = new ConfigurationPropertiesBeans();
        beans.setBeanMetaDataStore(metaData);
        return beans;
    }

配合ConfigurationProperties註解的表演,其會緩存ApplicationContext上的全部含有ConfigurationProperties註解的bean。與第一點所提的ConfigurationPropertiesRebinder對象搭配使用


3.實例化結束後刷新父級ApplicationContext上的屬性

@Override
    public void afterSingletonsInstantiated() {
        // refresh parent application context
        if (this.context.getParent() != null) {
            // TODO: make this optional? (E.g. when creating child contexts that prefer to
            // be isolated.)
            ConfigurationPropertiesRebinder rebinder = context
                    .getBean(ConfigurationPropertiesRebinder.class);
            for (String name : context.getParent().getBeanDefinitionNames()) {
                rebinder.rebind(name);
            }
        }
    }

EncryptionBootstrapConfiguration

與屬性讀取的加解密有關,跟JDK的keystore也有必定的關聯,具體就不去解析了。讀者可自行分析

Bean加載問題

此處本文插入這個Bean的加載問題,由於筆者發現SpringApplication會被調用兩次,那麼ApplicationContext實例也會被建立兩次。那麼基於@Configuration修飾過的自定義的Bean是否是也會被加載兩次呢??

通過在cloud環境下編寫了一個簡單的Bean

package com.example.clouddemo;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

/**
 * @author nanco
 * -------------
 * -------------
 * @create 19/8/20
 */
@Configuration
public class TestApplication implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("application parent context id: " + applicationContext.getParent().getId());
        System.out.println("application context id: " + applicationContext.getId());
    }
}

且在application.properties文件中指定spring.application.name=springChild以及bootstrap.properties文件中也指定spring.application.name=springBootstrap

運行main方法以後打印的關鍵信息以下

application parent context id: bootstrap
application context id: springBootstrap-1

通過在org.springframework.boot.context.ContextIdApplicationContextInitializer類上進行斷點調試發現只有用戶級ApplicationContext被建立的過程當中會實例化用戶自定義Bean。也就是說bootstrapContext並不會去實例化用戶自定義的Bean,這樣就很安全。

那麼爲什麼如此呢?其實很簡單,由於bootstrapContext指定的source類只有BootstrapImportSelectorConfiguration,並無用戶編寫的啓動類,也就沒法影響用戶級別Context的Bean加載實例化了~~~而且該類上無@EnableAutoConfiguration註解,代表其也不會去處理spring.factories文件中@EnableAutoConfiguration註解key對應的配置集合。

小結

1.針對springcloud context模塊下的以BootstrapConfiguration做爲Key的自動配置類,除了PropertySourceBootstrapConfiguration自動類的應用範圍在子級ApplicationContext,其它三個均有做用於父級ApplicationContext。

2.關於外部源文件的屬性,默認狀況下其有更高的優先級於本地系統以及環境變量。固然用戶也能夠經過修改spring.cloud.config.allowOverride/spring.cloud.config.overrideSystemProperties/spring.cloud.config.overrideNone屬性來進行優先級更改,經過此,用戶須要複寫PropertySourceLocator接口來進行配置

package com.example.cloud.external.resource;

import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;

import java.util.HashMap;
import java.util.Map;

/**
 * @author nanco
 * -------------
 * cloud-demo
 * -------------
 * @create 2019/1/15 19:40
 * @descrption
 **/
@Configuration
public class ExternalPropertySourceLocator implements PropertySourceLocator {

    private static final String EXTERNAL_KEY = "external";

    @Override
    public PropertySource<?> locate(Environment environment) {
        Map<String, Object> externalMap = new HashMap<>();

        // user custom property
        externalMap.put("username", "nanco");
        externalMap.put("password", "nanco123");
        externalMap.put("mail", "nancoasky@gmail.com");

        // application property
        externalMap.put("spring.cloud.config.allowOverride", true);
        externalMap.put("spring.cloud.config.overrideSystemProperties", false); //system擁有更高的優先級
        externalMap.put("spring.cloud.config.overrideNone", false);

        return new MapPropertySource(EXTERNAL_KEY, externalMap);
    }
}

最後將上述的類放置在META-INF/spring.factories文件中即可以生效了

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.example.cloud.external.resource.ExternalPropertySourceLocator

3.關於針對@ConfigurationProperties註解的Beans對象的刷新操做,本文只講解了JMX方式去調用,若是與第三方插件結合,應該會有更多的形式。

4.針對監聽器環節的分析到本章暫時告一段落,下一篇便分析cloud-context板塊spring.factories文件中以org.springframework.boot.autoconfigure.EnableAutoConfiguration做爲Key的類集合。 熟悉的氣息再次降臨,火燒眉毛ing....

相關文章
相關標籤/搜索