承接前文springcloud情操陶冶-springcloud context(二),本文將在前文基礎上淺析下ConfigServer的工作原理

前話

根據前文得知,bootstrapContext引入了PropertySourceLocator介面供外部源載入配置,但作用是應用於子級ApplicationContext的環境變數Environment上,並不做更新維護操作。

具體的載入與維護更新外部源的配置資訊,還是得有ConfigServer來完成,這也是本文分析的重點。

監聽器

在這之前,筆者先檢視此板塊關聯的監聽器ConfigServerBootstrapApplicationListener,因為其比前文分析的BootstrapApplicationListener監聽器優先順序還高,不過內部程式碼很簡單,筆者直接檢視其複寫的方法

	@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.resolvePlaceholders("${spring.cloud.config.enabled:false}")
.equalsIgnoreCase("true")) {
if (!environment.getPropertySources().contains(this.propertySource.getName())) {
environment.getPropertySources().addLast(this.propertySource);
}
}
}

程式碼意思很簡單,針對環境變數中spring.cloud.config.enabled屬性如果值不為true則設定為false。根據官方上的程式碼註釋來看,是用於遮蔽HTTP方式的訪問,預設是開啟遮蔽功能的。也就是遮蔽了ConfigServer暴露Restful方式的介面訪問,其中該屬性可通過System系統變數或者SpringApplicationBuilder類來進行設定,具體讀者可查閱其官方註釋

這個影響小,我們直接去檢視其如何去載入外部源的

BootstrapContext關聯類

優先分析與bootstrapContext相關的類,通過檢視其板塊下的spring.factories檔案對應的BootstrapConfiguration鍵值

# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.config.server.bootstrap.ConfigServerBootstrapConfiguration,\
org.springframework.cloud.config.server.config.EncryptionAutoConfiguration

筆者挑選ConfigServerBootstrapConfiguration類作為主要的分析源頭,內部的原始碼比較簡單,筆者則全部放出來

// 系統變數或者bootstrap.properties檔案指定了spring.cloud.config.server.bootstrap屬性則生效
@Configuration
@ConditionalOnProperty("spring.cloud.config.server.bootstrap")
public class ConfigServerBootstrapConfiguration { @EnableConfigurationProperties(ConfigServerProperties.class)
@Import({ EnvironmentRepositoryConfiguration.class })
protected static class LocalPropertySourceLocatorConfiguration { @Autowired
private EnvironmentRepository repository; @Autowired
private ConfigClientProperties client; @Autowired
private ConfigServerProperties server; // 載入外部源入口
@Bean
public EnvironmentRepositoryPropertySourceLocator environmentRepositoryPropertySourceLocator() {
return new EnvironmentRepositoryPropertySourceLocator(this.repository, this.client.getName(),
this.client.getProfile(), getDefaultLabel());
} private String getDefaultLabel() {
if (StringUtils.hasText(this.client.getLabel())) {
return this.client.getLabel();
} else if (StringUtils.hasText(this.server.getDefaultLabel())) {
return this.server.getDefaultLabel();
}
return null;
} } }

根據當前環境下是否存在spring.cloud.config.server.bootstrap屬性來決定是否通過Git/SVN/Vault等方式(下文將提及)載入外部源至子級的ConfigurableEnvironment物件中,預設不開啟,需要使用者配置。

具體通過什麼方式獲取外部資源則交由EnvironmentRepository介面去實現,我們先看下此介面的方法

public interface EnvironmentRepository {

	// 內部就一個方法,通過引數指定找尋對應的環境物件
Environment findOne(String application, String profile, String label); }

看來其支援多倉庫源的配置,但這裡注意一下此處的Environment回參是springcloud config client板塊中的類,應該是對我們常見的環境變數作些過濾的作用。

EnvironmentRepositoryConfiguration

除了上述的方式引入此多環境倉庫的配置類,ConfigServer對應的ConfigServerAutoConfiguration預設也會引入。廢話少說,首先看下頭部

@Configuration
@EnableConfigurationProperties({ SvnKitEnvironmentProperties.class, CredhubEnvironmentProperties.class,
JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class })
@Import({ CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class,
CredhubConfiguration.class, CredhubRepositoryConfiguration.class, SvnRepositoryConfiguration.class,
NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class, DefaultRepositoryConfiguration.class })
public class EnvironmentRepositoryConfiguration {
}

嗯,看起來很多,其實也就是針對不同源的資源進行相應的配置,比如常見的SVN/Jdbc/Git/Vault等方式。針對不同源,springcloud允許使用者配置spring.profile.active屬性來選擇相應的源,即使不指定,springcloud也預設以Git方式獲取倉庫。本文以springcloud預設支援的Git方式作為分析的入口

GitRepositoryConfiguration

git方式的資源獲取是通過配置GitRepositoryConfiguration類來實現的,筆者看下其程式碼

@Configuration
@Profile("git")
class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
}

直接去觀察其繼承的DefaultRepositoryConfiguration類,內部原始碼也很簡單,順便把其關聯的一些bean也一同放上來,方便我們更清楚的瞭解

	// 多Git環境倉庫屬性配置,以spring.cloud.config.server.git作為開頭
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public MultipleJGitEnvironmentProperties multipleJGitEnvironmentProperties() {
return new MultipleJGitEnvironmentProperties();
} @Configuration
@ConditionalOnClass(TransportConfigCallback.class)
static class JGitFactoryConfig { // 多Git環境倉庫的工廠類
@Bean
public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory(
ConfigurableEnvironment environment, ConfigServerProperties server,
Optional<ConfigurableHttpConnectionFactory> jgitHttpConnectionFactory,
Optional<TransportConfigCallback> customTransportConfigCallback) {
return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory,
customTransportConfigCallback);
}
} @Configuration
@ConditionalOnClass({ HttpClient.class, TransportConfigCallback.class })
static class JGitHttpClientConfig { // HTTP連線工廠類
@Bean
public ConfigurableHttpConnectionFactory httpClientConnectionFactory() {
return new HttpClientConfigurableHttpConnectionFactory();
}
} @Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
....
.... @Bean
public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
return gitEnvironmentRepositoryFactory.build(environmentProperties);
}
}

這裡註冊的MultipleJGitEnvironmentRepository物件便是EnvironmentRepository介面的實現類,由其統一管理多Git倉庫的資源。在分析此類之前,先對上述的程式碼作下分步驟的分析以免產生糊塗


1.多Git倉庫屬性配置MultipleJGitEnvironmentProperties,也就是配置Git倉庫的地址以及訪問方式等等。挑選比較重要的屬性用於歸納(多倉庫應用)

假設遠端倉庫地址為[email protected]:jtjsir/config_demo.git

spring.cloud.config.server.git.repos.A1.pattern=config*		#A1倉庫的匹配規則(匹配源{application}/{profile}),預設為下一點的name
spring.cloud.config.server.git.repos.A1.name=config_demo #A1倉庫的別名
[email protected]:jtjsir/config_demo.git #遠端git倉庫地址
spring.cloud.config.server.git.repos.A1.username=nancoasky@gmail.com #git帳號
spring.cloud.config.server.git.repos.A1.password=nanco123 #git密碼
spring.cloud.config.server.git.repos.A1.passphrase= #ssh密碼短語,預設為空
spring.cloud.config.server.git.repos.A1.basedir=/data/demo/cloud #本地儲存路徑
spring.cloud.config.server.git.repos.A1.defaultLabel=master #標籤,類似git的分支概念

具體的使用者可檢視MultipleJGitEnvironmentProperties類去詳細的檢視各個屬性的含義,同時也可以瞭解SSH方式的校驗


2.Http連線工廠類HttpClientConfigurableHttpConnectionFactory,主要是支援http/https的Git訪問方式。具體就不講解了,讀者可自行分析

MultipleJGitEnvironmentRepository

顧名思義,其實就是JGitEnvironmentRepository類的集合類,我們只需要關注其複寫的findOne()方法,附上真正去查詢相應配置資源的AbstractScmEnvironmentRepository#findOne()方法

	@Override
public synchronized Environment findOne(String application, String profile, String label) {
// 通過native方式去載入,也就是讀取遠端Git倉庫的本地copy
NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
new NativeEnvironmentProperties());
// 1.獲取本地git倉庫的查詢路徑
Locations locations = getLocations(application, profile, label);
delegate.setSearchLocations(locations.getLocations());
// 2.獲取屬性集合
Environment result = delegate.findOne(application, profile, "");
result.setVersion(locations.getVersion());
result.setLabel(label);
// 過濾下
return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
getUri());
}

筆者分析上述標註的兩點,分步驟來


1.獲取本地git倉庫的查詢路徑,對應的是JGitEnvironmentRepository#getLocations()方法

	@Override
public synchronized Locations getLocations(String application, String profile,
String label) {
// label代表git倉庫的分支,預設為master
if (label == null) {
label = this.defaultLabel;
}
// 重新整理本地git倉庫,蘊含了拉取遠端倉庫、更新的操作。使用到了uri屬性
String version = refresh(label);
// 使用到了basedir和searchPaths屬性
return new Locations(application, profile, label, version,
getSearchLocations(getWorkingDirectory(), application, profile, label));
}

上述的搜尋路徑格式如{basedir}/{searchPaths:/}。其中searchPaths的組合方式是{application}、{profile}、{label}的隨意拼裝,有很大的靈活性。比如

{basedir}/{application}/{profile}/{label:master}/
{basedir}/{application}-{profile}/{label:master}/
{basedir}/{label:master}/{application}/{profile}/ {basedir}/config_demo

其中{application}、{profile}、{label}屬性都是非必須的。

備註

如果使用者有多層目錄的要求,則只需要通過(_)來代替"/"即可。

比如searchPaths={application},如果有二級目錄則使用application(_)profile即可


2.獲取屬性集合,具體的如何去解析獲取相應的配置資訊且看NativeEnvironmentRepository#findOne()方法

	// 此時的label為空字串
@Override
public Environment findOne(String config, String profile, String label) {
// 專門解析${}符號
SpringApplicationBuilder builder = new SpringApplicationBuilder(
PropertyPlaceholderAutoConfiguration.class);
// 設定spring.profiles.active=profile
ConfigurableEnvironment environment = getEnvironment(profile);
builder.environment(environment);
builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
/** 設定spring.config.name=config,application
** 設定spring.config.location={basedir}/{searchPaths:/}
**
*/
String[] args = getArgs(config, profile, label);
// Explicitly set the listeners (to exclude logging listener which would change
// log levels in the caller)
builder.application()
.setListeners(Arrays.asList(new ConfigFileApplicationListener()));
ConfigurableApplicationContext context = builder.run(args);
environment.getPropertySources().remove("profiles");
try {
// 過濾系統內部的通用變數並縮減source對應的key
return clean(new PassthruEnvironmentRepository(environment).findOne(config,
profile, label));
}
finally {
context.close();
}
}

其實很簡單就是跟我們平常springboot啟動時一樣,讀取相應的配置檔案(此處只支援yml、properties、yaml方式),讀取的格式例子如下

{basedir}/{searchPaths:/}application.properties
{basedir}/{searchPaths:/}application.yml
{basedir}/{searchPaths:/}{application}.properties
{basedir}/{searchPaths:/}{application}.yml
{basedir}/{searchPaths:/}{application}-{profile}.yml
{basedir}/{searchPaths:/}{application}-{profile}.properties
---
{basedir}/{application}/{profile}/{label:master}/application.[properties|yml]
{basedir}/{application}-{profile}/{label:master}/{application}.[properties|yml]
{basedir}/{label:master}/{application}/{profile}/{application}-{profile}.[properties|yml]
---
{basedir}/config_demo/{application}-{profile}.[properties|yml]
{basedir}/config_demo/application.[properties|yml]

其中{application}、{profile}、{label}屬性都是非必須的。

小結

多倉庫的外部源載入方式本文是以git為例的,當然springcloud config支援多種方式的載入,有興趣的讀者可自行分析。

本文主要講解了ConfigServer如何去讀取相應的遠端Git檔案的邏輯以及讀取檔案的格式,具體可查閱上文,靈活性還是很強的。既然知道外部資源如何被載入,那麼如何被訪問也必須得了解下,下文則針對此作詳細的分析。

同時本文主要分析原始碼,具體的應用其實還是需要查閱官方文件,裡面對應用講的很仔細也很容易入門,就放個小入口,方便以後自己查閱!