Spring Cloud Config原码篇(十)
童话述说我的结局 人气:0上篇中说到通过@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()); }
加载全部内容