上篇中說到經過@Value註解獲取配置中心的內容進行注入,要想了解這個就要知道spring Environment原理,關於這原理我看了下網上分析的文章:https://blog.csdn.net/topdeveloperr/article/details/88063828java
首先來看第一部分,就是spring boot須要解析的外部資源文件的路徑是如何初始化的。在spring boot的啓動流程中,有一個 prepareEnvironment 方法,這個方法就是用來準備Environment這個對象的。找他的入中是從springApplication.run 開始的git
進入ConfigurableEnvironment類 這個方法主要就是建立和配置spring容器的環境信息web
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 .spring
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應用運行環境的實現類,後面全部的關於環境相關的配置操做都是基於這個類,它的類的結構圖以下bootstrap
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的地方。springboot
@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 命名來講,Mutable是一個可變的意思,也就是意味着它動態的管理了PropertySource的集合。app
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 這個方法,這個方法有兩個做用
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方法中
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()); }
這個方法作兩個事情
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader(environment, resourceLoader).load(); }
進入Load類load方法這個方法比較複雜,總的來講,就是加載全部可能的profiles首先咱們來看,這裏其實是調用了 FilteredPropertySource.apply 方法。而後傳遞了一個lambda表達式到apply方法中。
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表達式的主要邏輯是
(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 的優先級最低,系統變量和環境變量的優先級相對較高
在Spring Cloud Config中,經過@Value註解注入了一個屬性,可是這個屬性不存在於本地配置中,那麼Config是如何將遠程配置信息加載到Environment中的呢?這裏須要思考幾個問題
爲了解決這三個問題,Spring Cloud Config規範中定義了三個核心的接口
下面就來了解下Environment是如何在啓動過程當中從遠程服務器上加載配置的
從前面的代碼分析過程當中咱們知道,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時
source不爲空的狀況,纔會設置到environment中
@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; }
服務器端去遠程倉庫加載配置的流程就比較簡單了,核心接口是: 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方法,主要有兩個核心邏輯
@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()); }