专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Spring Cloud Config Client配置加载过程分析

本文基于 org.springframework.cloud:spring-cloud-config:2.1.0.RC3 版本进行分析。

1 BootstrapApplicationListener

首先在 spring-cloud-context包下找到 spring.factories 文件,其中有这样的配置:

org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener

程序启动时,会创建 SpringApplication 对象,此时会遍历 spring.factories 文件,将其中的 ApplicationListener 赋值给 listeners 属性。

public class BootstrapApplicationListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap";

    public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;

    public static final String DEFAULT_PROPERTIES = "defaultProperties";

    private int order = DEFAULT_ORDER;

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
                true)) {  // 1.1
            return;
        }
        // don't listen to events in a bootstrap context
        if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }
        ConfigurableApplicationContext context = null;
        String configName = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
        for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
                .getInitializers()) {
            if (initializer instanceof ParentContextApplicationContextInitializer) {
                context = findBootstrapContext(
                        (ParentContextApplicationContextInitializer) initializer,
                        configName);
            }
        }
        if (context == null) {
            context = bootstrapServiceContext(environment, event.getSpringApplication(),
                    configName);  // 1.2
            event.getSpringApplication().addListeners(new CloseContextOnFailureApplicationListener(context));
        }

        apply(context, event.getSpringApplication(), environment);  // 1.3
    }
}

BootstrapApplicationListener 监听 ApplicationEnvironmentPreparedEvent 事件;当执行 SpringApplication->prepareEnvironment 方法时,发布事件,执行 onApplicationEvent 方法。

1、1、 spring.cloud.bootstrap.enabled默认开启,如果设置为 false 的话,直接返回;

1、2、这里面加载了spring.factories文件中的BootstrapConfiguration

private ConfigurableApplicationContext bootstrapServiceContext(
            ConfigurableEnvironment environment, final SpringApplication application,
            String configName) {
    ...
    SpringApplicationBuilder builder = new SpringApplicationBuilder()
            .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
            .environment(bootstrapEnvironment)
            // Don't use the default properties in this builder
            .registerShutdownHook(false).logStartupInfo(false)
            .web(WebApplicationType.NONE);
    final SpringApplication builderApplication = builder.application();
    ...
    builder.sources(BootstrapImportSelectorConfiguration.class);  // 加载BootstrapConfiguration
    final ConfigurableApplicationContext context = builder.run();  
    ...
    return context;
}

1、3、把 PropertySourceBootstrapConfiguration加入到主 SpringApplication 的 initializers 属性中;当调用 SpringApplication -> prepareContext 时,会执行其initialize方法。

private void apply(ConfigurableApplicationContext context,
            SpringApplication application, ConfigurableEnvironment environment) {
    @SuppressWarnings("rawtypes")
    List<ApplicationContextInitializer> initializers = getOrderedBeansOfType(context,
            ApplicationContextInitializer.class);
    application.addInitializers(initializers
            .toArray(new ApplicationContextInitializer[initializers.size()]));
    addBootstrapDecryptInitializer(application);
}

2 PropertySourceBootstrapConfiguration

@Configuration
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME
            + "Properties";

    private int order = Ordered.HIGHEST_PRECEDENCE + 10;

    @Autowired(required = false)
    private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

    @Override
    public int getOrder() {
        return this.order;
    }

    public void setPropertySourceLocators(
            Collection<PropertySourceLocator> propertySourceLocators) {
        this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        CompositePropertySource composite = new CompositePropertySource(
                BOOTSTRAP_PROPERTY_SOURCE_NAME);  // 2.1
        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
        boolean empty = true;
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        for (PropertySourceLocator locator : this.propertySourceLocators) {
            PropertySource<?> source = null;
            source = locator.locate(environment);  // 2.2
            if (source == null) {
                continue;
            }
            logger.info("Located property source: " + source);
            composite.addPropertySource(source);  // 2.3
            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); // 2.4
            reinitializeLoggingSystem(environment, logConfig, logFile);
            setLogLevels(applicationContext, environment);
            handleIncludedProfiles(environment);
        }
    }
}

2、1、新建 name 为 bootstrapProperties 的 PropertySource

2、2、解析远程配置文件的内容;

public org.springframework.core.env.PropertySource<?> locate(
            org.springframework.core.env.Environment environment) {
    ConfigClientProperties properties = this.defaultProperties.override(environment);  //获取 ConfigClientProperties
    CompositePropertySource composite = new CompositePropertySource("configService");
    RestTemplate restTemplate = this.restTemplate == null
            ? getSecureRestTemplate(properties)
            : this.restTemplate;
    Exception error = null;
    String errorBody = null;
    try {
        String[] labels = new String[] { "" };
        if (StringUtils.hasText(properties.getLabel())) {
            labels = StringUtils
                    .commaDelimitedListToStringArray(properties.getLabel());
        }
        String state = ConfigClientStateHolder.getState();
        // Try all the labels until one works
        for (String label : labels) {
            Environment result = getRemoteEnvironment(restTemplate, properties,
                    label.trim(), state);  // 获取config-server中的配置文件信息,主要是根据 uri,微服务名,profile 环境,label 拼接 url,之后发送 http 请求获取信息
            if (result != null) {
                log(result);

                if (result.getPropertySources() != null) { 
                    for (PropertySource source : result.getPropertySources()) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> map = (Map<String, Object>) source
                                .getSource();
                        composite.addPropertySource(
                                new MapPropertySource(source.getName(), map));  // 信息保存在 CompositePropertySource 中
                    }
                }

                ...
                return composite;
            }
        }
    }
    ...
    return null;
}

private Environment getRemoteEnvironment(RestTemplate restTemplate,
            ConfigClientProperties properties, String label, String state) {
    String path = "/{name}/{profile}";
    String name = properties.getName();
    String profile = properties.getProfile();
    ...
    {
        final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
        response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
                Environment.class, args);  //拼接 url,发送 http 请求,获取 config-server 中的文件信息; 比如 uri=http://um31/config/, path= information-service/dev, 那么最终获取的就是 information-service 服务的 dev 环境的配置文件
        ...
        Environment result = response.getBody();
        return result;
    }
    return null;
}

2、3、将解析后的结果保存到 composite 中;

2、4、将新的配置加入到 env 环境;

private void insertPropertySources(MutablePropertySources propertySources,
            CompositePropertySource composite) {
    MutablePropertySources incoming = new MutablePropertySources();
    incoming.addFirst(composite);
    PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
    Binder.get(environment(incoming)).bind("spring.cloud.config", Bindable.ofInstance(remoteProperties));
    // 根据 remoteProperties 的值来确定配置文件的位置
    if (!remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone()
            && remoteProperties.isOverrideSystemProperties())) {
        propertySources.addFirst(composite);
        return;
    }
    if (remoteProperties.isOverrideNone()) {
        propertySources.addLast(composite);
        return;
    }
    if (propertySources
            .contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
        if (!remoteProperties.isOverrideSystemProperties()) {
            propertySources.addAfter(
                    StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                    composite);
        }
        else {
            propertySources.addBefore(
                    StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                    composite);
        }
    }
    else {
        propertySources.addLast(composite);
    }
}

3 PropertySourceBootstrapProperties

根据PropertySourceBootstrapProperties来确定配置文件在 enviroment 中的相对位置。

@ConfigurationProperties("spring.cloud.config")
public class PropertySourceBootstrapProperties {

    /**
     * Flag to indicate that the external properties should override system properties.
     * Default true.
     */
    private boolean overrideSystemProperties = true;

    /**
     * Flag to indicate that {@link #isOverrideSystemProperties()
     * systemPropertiesOverride} can be used. Set to false to prevent users from changing
     * the default accidentally. Default true.
     */
    private boolean allowOverride = true;

    /**
     * Flag to indicate that when {@link #setAllowOverride(boolean) allowOverride} is
     * true, external properties should take lowest priority and should not override any
     * existing property sources (including local config files). Default false.
     */
    private boolean overrideNone = false;
}

  • allowOverride 为 false,放最前面;overrideNone 为 false 并且 overrideSystemProperties 为 true,放最前面;
  • overrideNone 为 true,放最后面;
  • 如果包含 systemEnvironment 配置:overrideSystemProperties 为 false,那么放在 systemEnvironment 后面;否则放在 systemEnvironment 前面;
  • 其他情况放在最后面。

比如,设置 spring.cloud.config.overrideSystemProperties=false,那么 env 中 PropertySource 的次序是怎样的呢?

做个实验,

1、 application.yml 设置 spring.redis.password=gl001;
2、 主函数中配置 System.setProperty("spring.redis.password", "gl002");
3、 spring.cloud.config远程配置文件中设置 spring.redis.password=gl003
4、 程序启动时设置 --spring.redis.password=gl004

public static void main(String[] args) {
    System.setProperty("logging.level.org.springframework.core.env", "debug");
    System.setProperty("spring.redis.password", "gl002");
    ConfigurableApplicationContext context = SpringApplication.run(InfoServiceApplication.class, args);
    System.out.println("spring.redis.password: " + context.getEnvironment().getProperty("spring.redis.password"));
    Iterator<PropertySource<?>> iter = context.getEnvironment().getPropertySources().iterator();
    int i = 0;
    while (iter.hasNext()) {
        System.out.println((i++) + ":");
        PropertySource source = iter.next();
        if (source instanceof CompositePropertySource) {
            CompositePropertySource compositeSource = (CompositePropertySource) source;
            System.out.println("name: " + compositeSource.getName() + ", value: " + compositeSource.getPropertySources());
        } else {
            System.out.println("name: " + source.getName() + ", value: " + source.getSource());
        }
    }
}

部分运行结果如下:

spring.redis.password: gl002
4:
name: systemProperties, value: {spring.redis.password=gl002, java.vm.name=Java HotSpot(TM) 64-Bit Server VM, ...}
5:
name: systemEnvironment, value: {spring.redis.password=gl004, SHELL=/bin/zsh, ...}
6:
name: bootstrapProperties, value: [CompositePropertySource {name='configService', propertySources=[MapPropertySource@1306535359 {name='file:/home/config-center/a-service/a-service.yml', properties={spring.redis.password=gl003, ...}]}]
9:
name: applicationConfig: [classpath:/application.yml], value: {spring.redis.password=gl001, server.port=10000,...}

文章永久链接:https://tech.souyunku.com/25820

未经允许不得转载:搜云库技术团队 » Spring Cloud Config Client配置加载过程分析

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们