springCloudConfig

阅读前须知:

Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap, 另外一种是 application, bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。

SpringApplication.run

public ConfigurableApplicationContext run(String... args) {
    // 计时工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    //获取并启动监听器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 根据SpringApplicationRunListeners以及参数来准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = this.printBanner(environment);
        // 创建Spring容器
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        // Spring容器前置处理
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新容器
        this.refreshContext(context);
        // Spring容器后置处理
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        // 发出结束执行的事件
        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        // 执行Runners
        listeners.running(context);
        // 返回容器
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

见名知意,prepareEnvironment是准备environment配置源信息。

prepareEnvironment

springApplication.run -> prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // 根据上下文,创建一个合适的Environment对象
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置Environment的propertySource、以及profile
        configureEnvironment(environment, applicationArguments.getSourceArgs());
          ConfigurationPropertySources.attach(environment);
        // 通知监听器,加载配置文件(重要)
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

getOrCreateEnvironment

这个方法,就是根据当前的webApplication类型匹配对应的environment,如果是servlet,则是StandardServletEnvironment ,如果是spring webflux,则是 StandardReactiveWebEnvironment ,否则是StandardEnvironment。
此处是StandardServletEnvironment。

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }

StandardServletEnvironment是整个spring boot应用运行环境的实现类,后面所有的关于环境相关 的配置操作都是基于这个类,它的类的结构图如下:


StandardEnvironment.png

StandardServletEnvironment初始化

spring经常会在类初始化时做一些小动作,StandardServletEnvironment也不例外。
StandardServletEnvironment 会初始化父类 AbstractEnvironment ,在这个类的构造方法中,会 调用一个自定义配置文件的方法customizePropertySources。
customizePropertySources 这个方法被 StandardServletEnvironment 重写了,所以会调用StandardServletEnvironment 中的customizePropertySources 方法。
这里是将几个不同的配置源封装成 StubPropertySource 添加到 MutablePropertySources 中,调用 addLast 是表示一直往最后的位置添加。

  • SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet的配置信息,也就是在web.xml中servlet子标签init-param配置的信息。
  • SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们
    在web.xml中配置的 context-param 。
  • JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。

StandardServletEnvironment.customizePropertySources

protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }

继续调用父类,也就是 StandardEnvironment 类中的 customizePropertySources 方法。

  • SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: 系统变量,通过System.setProperty设置的变
    量,默认可以看到 java.version 、 os.name 等。
  • SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: 系统环境变量,也就是我们配置JAVA_HOME
    的地方。
protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(
                new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(
                new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

这里我们要明确一点,就是添加PropertySource的目的其实就是要告诉Environment,解析哪些位置的 属性文件进行加载。而在这个添加过程中,所有的添加都是基于 addLast ,也就是最早添加的 PropertySource会放在最前面。 systemProperties 是在 systemEnvironment 前面,这点很重要。因 为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高。

MutablePropertySources

在上面的代码中可以看到,所有的外部资源配置都是添加到了一个MutablePropertySources对象中, 这个对象封装了属性资源的集合。
而从 MutablePropertySources 命名来说,Mutable是一个可变的意思,也就是意味着它动态的管理了 PropertySource的集合。

public class MutablePropertySources implements PropertySources {

    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
//.......
}

接着,我们来看一下它怎么用的,找到 这个类,在这里定义了 MutablePropertySources。并且把这个MutablePropertySources作为参数传递给了
ConfigurablePropertyResolver 配置解析器中,而这个配置解析器是一个 PropertySourcesPropertyResolver 实例。

AbstractEnvironment

private final MutablePropertySources propertySources = new MutablePropertySources();

    private final ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);

我们来看一下这个类关系图, AbstractEnvironment 实现了文件解析器 ConfigurablePropertyResolver ,而在上面这段代码中我们把 MutablePropertySources 传递到 PropertySourcesPropertyResolver 中。这样就可以让 AbstractEnvironment 具备文件解析的功能,只是这个功能,委托给了PropertySourcesPropertyResolver来实现。

AbstractEnvironment.png

configureEnvironment

SpringApplication

通过上面的代码,spring构造了一个 StandardServletEnvironment 对象并且初始化了一些需要解析的propertySource。我们继续来看 configureEnvironment 这个方法,这个方法有三个作用

  • addConversionService 添加类型转化的服务,我们知道properties文件中配置的属性都是String 类型的,而转化为Java对象之后要根据合适的类型进行转化,而 ConversionService 是一套通用 的转化方案,这里把这个转化服务设置到当前的Environment,很显然,就是为Environment配置 解析时提供一个类型转化的解决方案
  • configurePropertySources 配置Environment中的propertysources
  • configureProfiles 配置profiles
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

configurePropertySources

  • 设置 defaultProperties 属性来源
  • 设置commandLineProperties来源,如果设置了命令行参数,则会加载SimpleCommandLinePropertySource 作为propertySource
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(
                        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

这个defaultProperties是什么呢?给大家演示一个东西,就是说我们可以设置一个默认的属性, 如果设置过,则需要加载。否则就不需要。

public static void main(String[] args) {
 SpringApplication springApplication=new SpringApplication(SpringCloudEurekaServerApplication.class);
 Map<String, Object> pro = new HashMap<>();
 pro.put("key", "value");
 springApplication.setDefaultProperties(pro);
 springApplication.run(args);
}

configureProfiles

这个方法就比较容易理解,就是配置当前激活的profiles,将当前的activeProfiles设置到enviroment 中。这样就能够使得我们完成不同环境下配置的获取问题。

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }

listeners.environmentPrepared(environment)

上述工作完成之后,发布一个environmentPrepared环境准备就绪的通知,这里是重点,需要详细说明一下,应为此处涉及bootstrap上下文的加载。
在SpringApplication的run方法启动中有以下代码,要回过头说明一下

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

getRunListeners方法,主要是使用spring的spi扩展机制加载事件发布类EventPublishingRunListener,并且在构造方法中,将所有的消息监听器加入到属性中。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,
                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

listeners.starting()就是发布一个ApplicationStartingEvent事件。
那么又有一个问题,SpringApplication的listeners在什么时候初始化的,看一下SpringApplication的构造方法。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //spi初始化了ApplicationContextInitializer
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //spi初始化了ApplicationListener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

有两个监听器比较重要,下面的代码是截取代码,不止此处的两个监听器。

org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener

并且BootstrapApplicationListener是第一个,ConfigFileApplicationListener一定是比BootstrapApplicationListener顺位低。
此时listeners.environmentPrepared(environment)发布一个ApplicationEnvironmentPreparedEvent事件,BootstrapApplicationListener和ConfigFileApplicationListener都有监听该事件。

BootstrapApplicationListener

onApplicationEvent

当监听事件触发后,构建bootstrap上下文。

public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment environment = event.getEnvironment();
        if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
                true)) {
            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) {
            //构建bootstrap上下文
            context = bootstrapServiceContext(environment, event.getSpringApplication(),
                    configName);
            event.getSpringApplication()
                    .addListeners(new CloseContextOnFailureApplicationListener(context));
        }
        //主要设置Initializers
        apply(context, event.getSpringApplication(), environment);
    }

bootstrapServiceContext

private ConfigurableApplicationContext bootstrapServiceContext(
            ConfigurableEnvironment environment, final SpringApplication application,
            String configName) {
        StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
        MutablePropertySources bootstrapProperties = bootstrapEnvironment
                .getPropertySources();
        for (PropertySource<?> source : bootstrapProperties) {
            bootstrapProperties.remove(source.getName());
        }
        String configLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
        String configAdditionalLocation = environment
                .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
        Map<String, Object> bootstrapMap = new HashMap<>();
        bootstrapMap.put("spring.config.name", configName);
        // if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
        // will fail
        // force the environment to use none, because if though it is set below in the
        // builder
        // the environment overrides it
        bootstrapMap.put("spring.main.web-application-type", "none");
        if (StringUtils.hasText(configLocation)) {
            bootstrapMap.put("spring.config.location", configLocation);
        }
        if (StringUtils.hasText(configAdditionalLocation)) {
            bootstrapMap.put("spring.config.additional-location",
                    configAdditionalLocation);
        }
//加载一个bootstrap数据源,重点,此配置源有一个属性spring.config.name:bootstrap
        bootstrapProperties.addFirst(
                new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
        for (PropertySource<?> source : environment.getPropertySources()) {
            if (source instanceof StubPropertySource) {
                continue;
            }
            bootstrapProperties.addLast(source);
        }
        // TODO: is it possible or sensible to share a ResourceLoader?
        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();
        if (builderApplication.getMainApplicationClass() == null) {
            // gh_425:
            // SpringApplication cannot deduce the MainApplicationClass here
            // if it is booted from SpringBootServletInitializer due to the
            // absense of the "main" method in stackTraces.
            // But luckily this method's second parameter "application" here
            // carries the real MainApplicationClass which has been explicitly
            // set by SpringBootServletInitializer itself already.
            builder.main(application.getMainApplicationClass());
        }
        if (environment.getPropertySources().contains("refreshArgs")) {
            // If we are doing a context refresh, really we only want to refresh the
            // Environment, and there are some toxic listeners (like the
            // LoggingApplicationListener) that affect global static state, so we need a
            // way to switch those off.
            builderApplication
                    .setListeners(filterListeners(builderApplication.getListeners()));
        }
//加载BootstrapImportSelectorConfiguration批量注册,即spi方式加载所有org.springframework.cloud.bootstrap.BootstrapConfiguration对应的类。
        builder.sources(BootstrapImportSelectorConfiguration.class);
 //SpringApplication的run方法
        final ConfigurableApplicationContext context = builder.run();
        // gh-214 using spring.application.name=bootstrap to set the context id via
        // `ContextIdApplicationContextInitializer` prevents apps from getting the actual
        // spring.application.name
        // during the bootstrap phase.
        context.setId("bootstrap");
        // Make the bootstrap context a parent of the app context
        addAncestorInitializer(application, context);
        // It only has properties in it now that we don't want in the parent so remove
        // it (and it will be added back later)
        bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
//enviorment的配置源合并
        mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
        return context;
    }

此时父容器开始加载,加载中会触发ApplicationEnvironmentPreparedEvent事件,此时因为BootstrapApplicationListener的onApplicationEvent中,发现envioronment中含有bootstrap,所以这个监听器跳过。

if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
            return;
        }

ConfigFileApplicationListener

onApplicationEvent

此时调用ConfigFileApplicationListener监听器,此监听器主要是从指定位置,加载指定名称的配置文件。

public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
                //将自身加入,自身也是一个拦截器
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }
    }

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        addPropertySources(environment, application.getResourceLoader());
    }

addPropertySources

最终执行到 ConfigFileApplicationListener.addPropertySources 方法中,这个方法做两个事情

  • 添加一个RandomValuePropertySource到Environment的MutablePropertySources中
  • 加载spring boot中的配置信息,比如bootstrap.yml/application.yml或者bootstrap.properties/application.properties
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        new Loader(environment, resourceLoader).load();
    }

Load.load

这个方法比较复杂,总的来说,就是加载所有可能的profiles
首先我们来看,这里实际上是调用了方法。然后传递了一个lambda
表达式到apply方法中。

  • 其中apply方法的主要逻辑是,判断如果当前如果有默认配置,则将默认配置员增加一个FilteredPropertySource.
  • 执行匿名内部类
void load() {
            FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
                    (defaultProperties) -> {
                        this.profiles = new LinkedList<>();
                        this.processedProfiles = new LinkedList<>();
                        this.activatedProfiles = false;
                        this.loaded = new LinkedHashMap<>();
                        initializeProfiles();
                        while (!this.profiles.isEmpty()) {
                            Profile profile = this.profiles.poll();
                            if (isDefaultProfile(profile)) {
                                addProfileToEnvironment(profile.getName());
                            }
                            load(profile, this::getPositiveProfileFilter,
                                    addToLoaded(MutablePropertySources::addLast, false));
                            this.processedProfiles.add(profile);
                        }
                        load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                        addLoadedPropertySources();
                        applyActiveProfiles(defaultProperties);
                    });
        }

下面这个lambda表达式的主要逻辑是

  • 调用initializeProfiles初始化默认的Profile,没有设置的话就用默认,初始化之后保存到private Deque<Profile> profiles中,它是一个LIFO队列。因为profiles采用了LIFO队列,后进先出。所以会优先加载profile为null的配置文件,也就是匹配bootstrap.yml/application.yml或者bootstrap.properties/application.properties
  • 如果profiles不为空,则循环遍历每一个profiles,调用 load方法进行加载
 (defaultProperties) -> {
// 未处理的数据集合
this.profiles = new LinkedList<>();
// 已处理的数据集合
this.processedProfiles = new LinkedList<>(); this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>(); //加载存在已经激活的 profiles
initializeProfiles();
while (!this.profiles.isEmpty()) {//遍历所有profiles
        Profile profile = this.profiles.poll();
        if (isDefaultProfile(profile)) {
            addProfileToEnvironment(profile.getName());
        }
// 确定搜索范围,获取对应的配置文件名,并使用相应加载器加载 load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
// 将处理完的 profile添加到 processedProfiles列表当中,表示已经处理完成 this.processedProfiles.add(profile);
}
    load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources(); applyActiveProfiles(defaultProperties);// 更新 activeProfiles列表

Load.initializeProfiles

该方法的作用是加载存在已经激活的 profiles

private void initializeProfiles() {
   // The default profile for these purposes is represented as null. We add it
   // first so that it is processed first and has lowest priority.
   this.profiles.add(null);
   Binder binder = Binder.get(this.environment);
   //判断当前环境是否配置 spring.profiles.active属性
   Set<Profile> activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY);
   //判断当前环境是否配置 spring.profiles.include属性
   Set<Profile> includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY);
   //如果没有特别指定的话,就是 application.properties 和 application-default.properties配置
   List<Profile> otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty);
   this.profiles.addAll(otherActiveProfiles);
   // Any pre-existing active profiles set via property sources (e.g.
   // System properties) take precedence over those added in config files.
   this.profiles.addAll(includedViaProperty);
   addActiveProfiles(activatedViaProperty);
   // 如果 profiles集仍然为null,即没有指定,就会创建默认的profile
   if (this.profiles.size() == 1) { // only has null profile
      for (String defaultProfileName : this.environment.getDefaultProfiles()) {
         Profile defaultProfile = new Profile(defaultProfileName, true);
         this.profiles.add(defaultProfile);
      }
   }
}

load

继续跟进load方法,通过 getSearchLoacations 进行搜索,并且进行迭代。

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            getSearchLocations().forEach((location) -> {
                boolean isDirectory = location.endsWith("/");
                Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
                names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
            });
        }

getSearchLocations的主要功能,就是获取需要遍历的目标路径,默认情况下,会去 DEFAULT_SEARCH_LOCATIONS中查找,也就是

private Set<String> getSearchLocations() {
            Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
            if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
                locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY));
            }
            else {
                locations.addAll(
                        asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
            }
            return locations;
        }

    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";

getSearchNames

还记得BootstrapApplicationListener设置在enviorment中的spring.config.name吗?当时设置了bootstrap,此处就是获取该值作为配置文件名!约定大于设置!如果没有该值才会去加载application配置文件。

private Set<String> getSearchNames() {
            if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
                String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
                Set<String> names = asResolvedSet(property, null);
                names.forEach(this::assertValidConfigName);
                return names;
            }
            return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
        }

拿到文件名和路径地址之后,再拼接对应路径,选择合适的yml或者properties解析器进行解析。

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
                DocumentConsumer consumer) {
            if (!StringUtils.hasText(name)) {
                for (PropertySourceLoader loader : this.propertySourceLoaders) {
                    if (canLoadFileExtension(loader, location)) {
                        load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
                        return;
                    }
                }
                throw new IllegalStateException("File extension of config file location '" + location
                        + "' is not known to any PropertySourceLoader. If the location is meant to reference "
                        + "a directory, it must end in '/'");
            }
            Set<String> processed = new HashSet<>();
            for (PropertySourceLoader loader : this.propertySourceLoaders) {
                for (String fileExtension : loader.getFileExtensions()) {
                    if (processed.add(fileExtension)) {
                        loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                                consumer);
                    }
                }
            }
        }

propertySourceLoaders

propertySourceLoaders使用spi方式加载yml和properties文件的加载类

org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
            this.environment = environment;
//占位符解析器
            this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
//文件加载器
            this.resourceLoader = (resourceLoader != null) ? resourceLoader
                    : new DefaultResourceLoader(getClass().getClassLoader());
//文件内容解析器
            this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
                    getClass().getClassLoader());
        }

此时BootstrapApplicationListener的bootstrap.yml/properties解析完毕后调用mergeDefaultProperties进行配置源合并,然后调用apply方法,将BootstrapConfiguration下的ApplicationContextInitializer类加入到SpringApplication的Initializers中。

BootstrapApplicationListener.apply

private void apply(ConfigurableApplicationContext context,
            SpringApplication application, ConfigurableEnvironment environment) {
        if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
            return;
        }
        application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
        @SuppressWarnings("rawtypes")
        Set target = new LinkedHashSet<>(application.getInitializers());
            //加载ApplicationContextInitializer
        target.addAll(
                getOrderedBeansOfType(context, ApplicationContextInitializer.class));
        application.setInitializers(target);
        addBootstrapDecryptInitializer(application);
    

有一个重点ApplicationContextInitializer

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration

然后bootstrap加载完毕,并移除了bootstrap配置源(和bootstrap.yml/properties不是一个)。

application

bootstrap加载完毕后,application上下文继续加载,紧接ApplicationEnvironmentPreparedEvent,此时该事件该由ConfigFileApplicationListener继续处理,此时将会加载application.yml/properties,漫漫长路走了一半,系统内的文件加载完毕,那么怎么加载外部配置,比如配置了springcloudconfig。

prepareContext

SpringApplication.prepareContext

此方法主要看applyInitializers启动所有initializers,对,就是在bootstrap中加载的一个initializer,此处需要使用到了。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
  //启动所有Initializers
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

protected void applyInitializers(ConfigurableApplicationContext context) {
//获取并循环启动
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

PropertySourceBootstrapConfiguration

initialize

public void initialize(ConfigurableApplicationContext applicationContext) {
        List<PropertySource<?>> composite = new ArrayList<>();
//对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
        AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
        boolean empty = true;
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
//获取所有的propertySourceLocators,PropertySourceLocator是自动注入进来的
        for (PropertySourceLocator locator : this.propertySourceLocators) {
            Collection<PropertySource<?>> source = locator.locateCollection(environment);
            if (source == null || source.size() == 0) {
                continue;
            }
            List<PropertySource<?>> sourceList = new ArrayList<>();
            for (PropertySource<?> p : source) {
                sourceList.add(new BootstrapPropertySource<>(p));
            }
            logger.info("Located property source: " + sourceList);
            composite.addAll(sourceList);
            empty = false;
        }
        if (!empty) {
            MutablePropertySources propertySources = environment.getPropertySources();
            String logConfig = environment.resolvePlaceholders("${logging.config:}");
            LogFile logFile = LogFile.get(environment);
            for (PropertySource<?> p : environment.getPropertySources()) {
                if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                    propertySources.remove(p.getName());
                }
            }
            insertPropertySources(propertySources, composite);
            reinitializeLoggingSystem(environment, logConfig, logFile);
            setLogLevels(applicationContext, environment);
            handleIncludedProfiles(environment);
        }
    }

有一个PropertySourceLocator使用BootstrapConfiguration注入进来

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.client.ConfigServiceBootstrapConfiguration,\
org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration

这个配置类中注入了ConfigServicePropertySourceLocator

@Bean
    @ConditionalOnMissingBean(ConfigServicePropertySourceLocator.class)
    @ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
    public ConfigServicePropertySourceLocator configServicePropertySource(
            ConfigClientProperties properties) {
        ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
                properties);
        return locator;
    }

ConfigServicePropertySourceLocator

locateCollection

public Collection<org.springframework.core.env.PropertySource<?>> locateCollection(
            org.springframework.core.env.Environment environment) {
        return PropertySourceLocator.locateCollection(this, environment);
    }

PropertySourceLocator.locateCollection
这个方法会调用子类的locate方法,来获得一个PropertySource,然后将PropertySource集合返回。接 着它会调用 ConfigServicePropertySourceLocator 的locate方法。

static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
            Environment environment) {
        PropertySource<?> propertySource = locator.locate(environment);
        if (propertySource == null) {
            return Collections.emptyList();
        }
        if (CompositePropertySource.class.isInstance(propertySource)) {
            Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
                    .getPropertySources();
            List<PropertySource<?>> filteredSources = new ArrayList<>();
            for (PropertySource<?> p : sources) {
                if (p != null) {
                    filteredSources.add(p);
                }
            }
            return filteredSources;
        }
        else {
            return Arrays.asList(propertySource);
        }
    }

ConfigServicePropertySourceLocator.locate

这个就是Config Client的关键实现了,它会通过RestTemplate调用一个远程地址获得配置信息, getRemoteEnvironment 。然后把这个配置PropertySources,然后将这个信息包装成一个OriginTrackedMapPropertySource,设置到 Composite 中。

public org.springframework.core.env.PropertySource<?> locate(
            org.springframework.core.env.Environment environment) {
        ConfigClientProperties properties = this.defaultProperties.override(environment);
        CompositePropertySource composite = new OriginTrackedCompositePropertySource(
                "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);
                if (result != null) {
                    log(result);

                    // result.getPropertySources() can be null if using xml
                    if (result.getPropertySources() != null) {
                        for (PropertySource source : result.getPropertySources()) {
                            @SuppressWarnings("unchecked")
                            Map<String, Object> map = translateOrigins(source.getName(),
                                    (Map<String, Object>) source.getSource());
                            composite.addPropertySource(
                                    new OriginTrackedMapPropertySource(source.getName(),
                                            map));
                        }
                    }

                    if (StringUtils.hasText(result.getState())
                            || StringUtils.hasText(result.getVersion())) {
                        HashMap<String, Object> map = new HashMap<>();
                        putValue(map, "config.client.state", result.getState());
                        putValue(map, "config.client.version", result.getVersion());
                        composite.addFirstPropertySource(
                                new MapPropertySource("configClient", map));
                    }
                    return composite;
                }
            }
            errorBody = String.format("None of labels %s found", Arrays.toString(labels));
        }
        catch (HttpServerErrorException e) {
            error = e;
            if (MediaType.APPLICATION_JSON
                    .includes(e.getResponseHeaders().getContentType())) {
                errorBody = e.getResponseBodyAsString();
            }
        }
        catch (Exception e) {
            error = e;
        }
        if (properties.isFailFast()) {
            throw new IllegalStateException(
                    "Could not locate PropertySource and the fail fast property is set, failing"
                            + (errorBody == null ? "" : ": " + errorBody),
                    error);
        }
        logger.warn("Could not locate PropertySource: "
                + (error != null ? error.getMessage() : errorBody));
        return null;

    }

然后在PropertySourceBootstrapConfiguration.insertPropertySources(propertySources, composite)方法中将从配置中心获取的配置设置到首位,即调用了addfirst方法。

Config Server获取配置过程

服务器端去远程仓库加载配置的流程就比较简单了,核心接口是: ,提供了 配置读取的功能。我们先从请求入口开始看

EnvironmentController

Spring Cloud Config Server提供了EnvironmentController,这样通过在浏览器访问即可从git中获取配 置信息
在这个controller中,提供了很多的映射,最终会调用的是 getEnvironment 。

public Environment getEnvironment(String name, String profiles, String label,
            boolean includeOrigin) {
        name = Environment.normalize(name);
        label = Environment.normalize(label);
        Environment environment = this.repository.findOne(name, profiles, label,
                includeOrigin);
        if (!this.acceptEmpty
                && (environment == null || environment.getPropertySources().isEmpty())) {
            throw new EnvironmentNotFoundException("Profile Not found");
        }
        return environment;
    }

this.repository.findOne ,调用某个repository存储组件来获得环境配置信息进行返回。
repository是一个 EnvironmentRepository 对象,它有很多实现,其中就包含 RedisEnvironmentRepository 、 JdbcEnvironmentRepository 等。默认实现是 MultipleJGitEnvironmentRepository ,表示多个不同地址的git数据源。

MultipleJGitEnvironmentRepository.findOne

MultipleJGitEnvironmentRepository 代理遍历每个 JGitEnvironmentRepository, JGitEnvironmentRepository 下使用 NativeEnvironmentRepository 代理读取本地文件。

public Environment findOne(String application, String profile, String label,
            boolean includeOrigin) {
//遍历所有git
        for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
            if (repository.matches(application, profile, label)) {
                for (JGitEnvironmentRepository candidate : getRepositories(repository,
                        application, profile, label)) {
                    try {
                        if (label == null) {
                            label = candidate.getDefaultLabel();
                        }
                        Environment source = candidate.findOne(application, profile,
                                label, includeOrigin);
                        if (source != null) {
                            return source;
                        }
                    }
                    catch (Exception e) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug(
                                    "Cannot load configuration from " + candidate.getUri()
                                            + ", cause: (" + e.getClass().getSimpleName()
                                            + ") " + e.getMessage(),
                                    e);
                        }
                        continue;
                    }
                }
            }
        }
        JGitEnvironmentRepository candidate = getRepository(this, application, profile,
                label);
        if (label == null) {
            label = candidate.getDefaultLabel();
        }
        if (candidate == this) {
            return super.findOne(application, profile, label, includeOrigin);
        }
        return candidate.findOne(application, profile, label, includeOrigin);
    }

AbstractScmEnvironmentRepository.findOne

调用抽象类的findOne方法,主要有两个核心逻辑

  • 调用getLocations从GIT远程仓库同步到本地
  • 使用 NativeEnvironmentRepository 委托来读取本地文件内容
public synchronized Environment findOne(String application, String profile,
            String label, boolean includeOrigin) {
        NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(
                getEnvironment(), new NativeEnvironmentProperties());
        Locations locations = getLocations(application, profile, label);
        delegate.setSearchLocations(locations.getLocations());
        Environment result = delegate.findOne(application, profile, "", includeOrigin);
        result.setVersion(locations.getVersion());
        result.setLabel(label);
        return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
                getUri());
    }

JGitEnvironmentRepository.getLocations

public synchronized Locations getLocations(String application, String profile,
            String label) {
        if (label == null) {
            label = this.defaultLabel;
        }
        String version = refresh(label);
        return new Locations(application, profile, label, version,
                getSearchLocations(getWorkingDirectory(), application, profile, label));
    }

JGitEnvironmentRepository.refresh

看到checkout的时候,就有了悟了的感觉,原来这货能保证每次获取的都是最新配置的原因,原来是这货每次都去checkout!

public String refresh(String label) {
        Git git = null;
        try {
            git = createGitClient();
            if (shouldPull(git)) {
                FetchResult fetchStatus = fetch(git, label);
                if (this.deleteUntrackedBranches && fetchStatus != null) {
                    deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(),
                            git);
                }
                // checkout after fetch so we can get any new branches, tags, ect.
                checkout(git, label);
                tryMerge(git, label);
            }
            else {
                // nothing to update so just checkout and merge.
                // Merge because remote branch could have been updated before
                checkout(git, label);
                tryMerge(git, label);
            }
            // always return what is currently HEAD as the version
            return git.getRepository().findRef("HEAD").getObjectId().getName();
        }
        catch (RefNotFoundException e) {
            throw new NoSuchLabelException("No such label: " + label, e);
        }
        catch (NoRemoteRepositoryException e) {
            throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
        }
        catch (GitAPIException e) {
            throw new NoSuchRepositoryException(
                    "Cannot clone or checkout repository: " + getUri(), e);
        }
        catch (Exception e) {
            throw new IllegalStateException("Cannot load environment", e);
        }
        finally {
            try {
                if (git != null) {
                    git.close();
                }
            }
            catch (Exception e) {
                this.logger.warn("Could not close git repository", e);
            }
        }
    }

至此,springcloudconfig解析完毕。

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350