springcloud情操陶冶-springcloud config server(一)

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

前話

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

具體的加載與維護更新外部源的配置信息,仍是得有ConfigServer來完成,這也是本文分析的重點。git

監聽器

在這以前,筆者先查看此板塊關聯的監聽器ConfigServerBootstrapApplicationListener,由於其比前文分析的BootstrapApplicationListener監聽器優先級還高,不過內部代碼很簡單,筆者直接查看其複寫的方法github

@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類來進行設置,具體讀者可查閱其官方註釋web

這個影響小,咱們直接去查看其如何去加載外部源的spring

BootstrapContext關聯類

優先分析與bootstrapContext相關的類,經過查看其板塊下的spring.factories文件對應的BootstrapConfiguration鍵值bootstrap

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

筆者挑選ConfigServerBootstrapConfiguration類做爲主要的分析源頭,內部的源碼比較簡單,筆者則所有放出來springboot

// 系統變量或者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對象中,默認不開啓,須要用戶配置。app

具體經過什麼方式獲取外部資源則交由EnvironmentRepository接口去實現,咱們先看下此接口的方法ssh

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倉庫的地址以及訪問方式等等。挑選比較重要的屬性用於概括(多倉庫應用)

假設遠程倉庫地址爲git@github.com: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倉庫的別名
spring.cloud.config.server.git.repos.A1.uri=git@github.com: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文件的邏輯以及讀取文件的格式,具體可查閱上文,靈活性仍是很強的。既然知道外部資源如何被加載,那麼如何被訪問也必須得了解下,下文則針對此做詳細的分析。

同時本文主要分析源碼,具體的應用其實仍是須要查閱官方文檔,裏面對應用講的很仔細也很容易入門,就放個小入口,方便之後本身查閱!

相關文章
相關標籤/搜索