承接前文 springcloud 情操陶冶 - springcloud context(二), 本文将在前文基础上浅析下 ConfigServer 的工作原理
前话
根据前文得知, bootstrapContext 引入了 PropertySourceLocator 接口供外部源加载配置, 但作用是应用于子级 ApplicationContext 的环境变量 Environment 上, 并不做更新维护操作.
具体的加载与维护更新外部源的配置信息, 还是得有 ConfigServer 来完成, 这也是本文分析的重点.
监听器
在这之前, 笔者先查看此板块关联的监听器, 因为其比前文分析的 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 仓库的地址以及访问方式等等. 挑选比较重要的属性用于归纳(多仓库应用)
假设远程仓库地址为 Git@GitHub.com:jtjsir/config_demo.Git
- spring.cloud.config.server.Git.repos.A1.pattern=config* #A1 仓库的查找规则, 默认为下一点的 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 启动时一样, 读取相应的配置文件(此处只支持 YAML,properties,YAML 方式), 读取的格式例子如下
- {
- basedir
- }/{
- searchPaths:/
- }application.properties
- {
- basedir
- }/{
- searchPaths:/
- }application.YAML
- {
- basedir
- }/{
- searchPaths:/
- }{
- application
- }.properties
- {
- basedir
- }/{
- searchPaths:/
- }{
- application
- }.YAML
- {
- basedir
- }/{
- searchPaths:/
- }{
- application
- }-{
- profile
- }.YAML
- {
- basedir
- }/{
- searchPaths:/
- }{
- application
- }-{
- profile
- }.properties
- ---
- {
- basedir
- }/{
- application
- }/{
- profile
- }/{
- label:master
- }/application.[properties|YAML]
- {
- basedir
- }/{
- application
- }-{
- profile
- }/{
- label:master
- }/{
- application
- }.[properties|YAML]
- {
- basedir
- }/{
- label:master
- }/{
- application
- }/{
- profile
- }/{
- application
- }-{
- profile
- }.[properties|YAML]
- ---
- {
- basedir
- }/config_demo/{
- application
- }-{
- profile
- }.[properties|YAML]
- {
- basedir
- }/config_demo/application.[properties|YAML]
其中 {application},{profile},{label} 属性都是非必须的.
小结
多仓库的外部源加载方式本文是以 Git 为例的, 当然 springcloud config 支持多种方式的加载, 有兴趣的读者可自行分析.
本文主要讲解了 ConfigServer 如何去读取相应的远程 Git 文件的逻辑以及读取文件的格式, 具体可查阅上文, 灵活性还是很强的. 既然知道外部资源如何被加载, 那么如何被访问也必须得了解下, 下文则针对此作详细的分析.
同时本文主要分析源码, 具体的应用其实还是需要查阅官方文档, 里面对应用讲的很仔细也很容易入门, 就放个小入口, 方便以后自己查阅!
来源: https://www.cnblogs.com/question-sky/p/10304279.html