上篇中说到通过@Value注解获取配置中心的内容进行注入,要想了解这个就要知道spring Environment原理,关于这原理我看了下网上分析的文章:https://blog.csdn.net/topdeveloperr/article/details/88063828

一、Environment的初始化

首先来看第一部分,就是spring boot需要解析的外部资源文件的路径是如何初始化的。在spring boot的启动流程中,有一个 prepareEnvironment 方法,这个方法就是用来准备Environment这个对象的。找他的入中是从springApplication.run 开始的

进入ConfigurableEnvironment类 这个方法主要就是创建和配置spring容器的环境信息

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,当前默认的应该就是StandardServletEnvironment ,如果是spring webflux,则是StandardReactiveWebEnvironment .

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应用运行环境的实现类,后面所有的关于环境相关的配置操作都是基于这个类,它的类的结构图如下

StandardServletEnvironment的初始化过程会做一些事情,就是配置一些基本的属性来源。StandardServletEnvironment 会初始化父类 AbstractEnvironment ,在这个类的构造方法中,会调用一个自定义配置文件的方法,这个是spring中比较常见的实现手法,前面在看ribbon、eureka中都有看到。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
}

customizePropertySources 这个方法被 StandardServletEnvironment 重写了,所以会调用StandardServletEnvironment 中的 customizePropertySources 方法。不难看出,这里是将几个不同的配置源封装成 StubPropertySource 添加到
MutablePropertySources 中,调用 addLast 是表示一直往最后的位置添加。SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet的配置信息,也就是在中配置的SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们在web.xml中配置的 context-param 。JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。

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的地方。

    @Override
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会放在最前面。 systemEnvironment 是在 systemProperties 前面,这点很重要。因为前面的配置会覆盖后面的配置,也就是说系统变量中的配置比系统环境变量中的配置优先级更高

二、MutablePropertySources

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

public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new
CopyOnWriteArrayList<>();
}

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

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
  private final MutablePropertySources propertySources = new
MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
}

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

通过上面的代码,spring构造了一个 StandardServletEnvironment 对象并且初始化了一些需要解析的propertySource,现在回退到SpringApplication类的prepareEnvironment方法,我们继续来看  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));
   }
 }
}

到目前为止,还是在初始化外部化配置的数据来源。接着进入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));
}

经过上面的操作spring的配置信息都已加载完成,但有一个很重要的配置还没有加载,那就是springboot的配置信息,现在回退到SpringApplication类的prepareEnvironment类

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//springboot的发布事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
    void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}

选择EventPublishingRunListener类的environmentPrepared,进入事件的监听

    @Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

选择multicastEvent,进入SimpleApplicationEventMulticaster类的multicastEvent方法,这个方法是多纬度的监听

    @Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
    @Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
//得到结果集
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
//反射调用
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}

上面事件有反射调用就一定会有一个监听,如果有兴趣可以Debugger会发现这个getApplicationListeners的事件监听中有一个叫ConfigFileApplicationListener,这个监听器就是用来处理项目配置的,进入ConfigFileApplicationListener类会看到一个onApplicationEvent方法

    @Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
//环境的准备事件
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
//Appliaction的准备事件
onApplicationPreparedEvent(event);
}
}

如果有人在我之前说的要debugger的地方debugger的话会发现,现在发布的事件是一个ApplicationEnvironmentPreparedEvent事件,进入onApplicationEnvironmentPreparedEvent事件中

    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());
}
}

最终执行到 ConfigFileApplicationListener.addPropertySources 方法中

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

这个方法做两个事情

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

进入Load类load方法这个方法比较复杂,总的来说,就是加载所有可能的profiles首先我们来看,这里实际上是调用了 FilteredPropertySource.apply 方法。然后传递了一个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,没有设置的话就用默认,初始化之后保存到 privateDeque<Profile> profiles; 中,它是一个LIFO队列。 因为 profiles 采用了 LIFO 队列,后进先出。所以会先加载profile为null的配置文件 ,也就是匹配 application.properties、application.yml 。
  • 如果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列表

点击上面的 initializeProfiles,进入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类的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;
}

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

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));
 });
}

整理流程如下:
1)获取默认的配置文件路径,有4种。

2)遍历所有的路径,拼装配置文件名称。

3)再遍历解析器,选择yml或者properties解析,将解析结果添加到集合MutablePropertySources当中。

至此,springBoot中的资源文件加载完毕,解析顺序从上到下,所以前面的配置文件会覆盖后面的配置文件。可以看到 application.properties 的优先级最低,系统变量和环境变量的优先级相对较高

三、config中的environment

在Spring Cloud Config中,通过@Value注解注入了一个属性,但是这个属性不存在于本地配置中,那么Config是如何将远程配置信息加载到Environment中的呢?这里需要思考几个问题

  • 如何将配置加载到 Environment
  • 配置变更时,如何控制 Bean 是否需要 create,重新触发一次 Bean 的初始化,才能将 @Value 注解指定的字段从 Environment 中重新注入。
  • 配置变更时,如何控制新的配置会更新到 Environment 中,才能保证配置变更时可注入最新的值。

为了解决这三个问题,Spring Cloud Config规范中定义了三个核心的接口

  • PropertySourceLocator:抽象出这个接口,就是让用户可定制化的将一些配置加载到Environment。这部分的配置获取遵循了 Spring Cloud Config 的理念,即希望能从外部储存介质中来 loacte。
  • RefreshScope: Spring Cloud 定义这个注解,是扩展了 Spring 原有的 Scope 类型。用来标识当前这个 Bean 是一个refresh 类型的 Scope。其主要作用就是可以控制 Bean 的整个生命周期。
  • ContextRefresher:抽象出这个 Class,是让用户自己按需来刷新上下文(比如当有配置刷新时,希望可以刷新上下文,将最新的配置更新到 Environment,重新创建 Bean 时,就可以从Environment 中注入最新的配置)。

下面就来了解下Environment是如何在启动过程中从远程服务器上加载配置的

3.1、Config Client 配置加载过程

从前面的代码分析过程中我们知道,Environment中所有外部化配置,针对不同类型的配置都会有与之对应的PropertySource,比如(SystemEnvironmentPropertySource、CommandLinePropertySource)。以及PropertySourcesPropertyResolver来进行解析。
那Config Client在启动的时候,必然也会需要从远程服务器上获取配置加载到Environment中,这样才能使得应用程序通过@value进行属性的注入,而且我们一定可以猜测到的是,这块的工作一定又和spring中某个机制有关系。

在spring boot项目启动时,有一个prepareContext的方法,它会回调所有实现了ApplicationContextInitializer 的实例,来做一些初始化工作。

public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//回调所有实现
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

前面说过prepareContext的方法,它会回调所有实现了ApplicationContextInitializer 的实例然而PropertySourceBootstrapConfiguration 实现了 ApplicationContextInitializer 接口,其目的就是在应用程序上下文初始化的时候做一些额外的操作.根据默认的 AnnotationAwareOrderComparator 排序规则对propertySourceLocators数组进行排序,获取运行的环境上下文ConfigurableEnvironment,遍历propertySourceLocators时

  • 调用 locate 方法,传入获取的上下文environment
  • 将source添加到PropertySource的链表中
  • 设置source是否为空的标识标量empty

source不为空的情况,才会设置到environment中

  • 返回Environment的可变形式,可进行的操作如addFirst、addLast
  • 移除propertySources中的bootstrapProperties
  • 根据config server覆写的规则,设置propertySources
  • 处理多个active profiles的配置信息
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
  List<PropertySource<?>> composite = new ArrayList<>();
  //对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
  AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
  boolean empty = true;
  //获取运行的环境上下文
  ConfigurableEnvironment environment = applicationContext.getEnvironment();
  for (PropertySourceLocator locator : this.propertySourceLocators) {
    //回调所有实现PropertySourceLocator接口实例的locate方法,
    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);//将source添加到数组
    empty = false; //表示propertysource不为空
 }
  //只有propertysource不为空的情况,才会设置到environment中
  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);
 }
}

选择locateCollection进入PropertySourceLoader类的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;
}

四、Config Server获取配置过程

服务器端去远程仓库加载配置的流程就比较简单了,核心接口是: EnvironmentRepository ,提供了配置读取的功能。先从请求入口开始看;pring 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类中 代理遍历每个 JGitEnvironmentRepository,JGitEnvironmentRepository 下使用 NativeEnvironmentRepository 代理读取本地文件。

@Override
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 委托来读取本地文件内容
@Override
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());
}

Spring Cloud Config原码篇(十)的更多相关文章

  1. SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)(Finchley版本)

    上一篇文章讲述了一个服务如何从配置中心读取文件,配置中心如何从远程git读取配置文件,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如 ...

  2. 【SpringCloud】第七篇: 高可用的分布式配置中心(Spring Cloud Config)

    前言: 必需学会SpringBoot基础知识 简介: spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选. ...

  3. 跟我学SpringCloud | 第七篇:Spring Cloud Config 配置中心高可用和refresh

    SpringCloud系列教程 | 第七篇:Spring Cloud Config 配置中心高可用和refresh Springboot: 2.1.6.RELEASE SpringCloud: Gre ...

  4. 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)

    上一篇文章讲述了一个服务如何从配置中心读取文件,配置中心如何从远程git读取配置文件,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如 ...

  5. 史上最简单的SpringCloud教程 | 第六篇: 分布式配置中心(Spring Cloud Config)

    一.简介 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件. 在Spring Cloud中,有分布式配置中心组件spring cloud confi ...

  6. 第六篇: 分布式配置中心(Spring Cloud Config)

    一.简介 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件. 在Spring Cloud中,有分布式配置中心组件spring cloud confi ...

  7. spring cloud连载第二篇之Spring Cloud Config

    Spring Cloud Config Spring Cloud Config为分布式服务提供了服务侧和客户侧的外部配置支持.通过Spring Cloud Config你可以有一个统一的地方来管理所有 ...

  8. SpringCloud教程 | 第六篇: 分布式配置中心(Spring Cloud Config)(Finchley版本)

    在上一篇文章讲述zuul的时候,已经提到过,使用配置服务来保存各个服务的配置文件.它就是Spring Cloud Config. 一.简介 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管 ...

  9. 【SpringCloud】第六篇: 分布式配置中心(Spring Cloud Config)

    前言: 必需学会SpringBoot基础知识 简介: spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选. ...

随机推荐

  1. android studio问题备注

    androidTestCompile 'com.android.support:support-annotations:25.3.1'configurations.all { resolutionSt ...

  2. .NET Core/.NET 5.0 析构函数依然有效?

    前言 最近看到小伙伴在.NET Core中用到了析构函数,不禁打一疑问,大部分情况下,即使在.NET Framework中都不会怎么用到析构函数,我想在.NET Core中是否还依然有效呢?随着时间推 ...

  3. Python中判断一个中文是否中文数字的方法

    Python内置功能非常强大,在字符串内置函数中提供了一个判断字符串是否全数字的方法,而且这个方法不只是简单判断阿拉伯数字,包括中文数字和全角的阿拉伯数字都认识,这个函数就是字符串的isnumeric ...

  4. 第8.12节 Python类中使用__dict__定义实例变量和方法

    上节介绍了使用实例的__dict__查看实例的自定义属性,其实还可以直接使用__dict__定义实例变量和实例方法. 一. 使用__dict__定义实例变量 语法: 对象名. dict[属性名] = ...

  5. flask中的return、过滤器详解

    之前吧一直学习flask的时候,一直不明白response是怎么产生,今天是明白了.retrun  哎呀,这个地方看着挺小心的东西, 蕴含的能量可不小啊.今天我详细总结总结. 先来写jinjia2语法 ...

  6. tensorflow 指定版本安装

    首先,建议在anaconda中创建虚拟环境,教程已写,参考上一篇 下载之前建议设置pip清华源(用以提速,可百度) 设置下载源 pip config set global.index-url http ...

  7. 基于Fisco-Bcos的区块链智能合约-简单案例实践

    一.智能合约介绍 智能合约是指把合同/协议条款以代码的形式电子化地放到区块链网络上.FISCO BCOS平台支持两种智能合约类型:Solidity智能合约与预编译智能合约 Solidity与Java类 ...

  8. ADF 第四篇:管道的执行和触发器

    Azure Data Factory 系列博客: ADF 第一篇:Azure Data Factory介绍 ADF 第二篇:使用UI创建数据工厂 ADF 第三篇:Integration runtime ...

  9. STL—— 容器(vector)的内存分配,声明时的普通构造&带参构造

    vector 的几种带参构造 & 初始化与内存分配: 1. 普通的带参构造: vector 的相关对象可以在声明时通过 vector 的带参构造函数进行内存分配,如下: 1 #include ...

  10. 马赛克密码破解——GitHub 热点速览 Vol.50

    作者:HelloGitHub-小鱼干 "xx"(爆粗口) 这个词是最能体现本人看到本周 GitHub 热点的心情的.那一天,看到用图片处理技术还原马赛克密码的 Depix 便惊为天 ...