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....

相关文章
相关标签/搜索