二十三.SpringCloudConfig源码-初始化配置

简介: 今天这篇文章我们来分析一下Spring Cloud Config 配置中心的源码,这应该是Spring Cloud Netflix的源码分析的最后一篇。下一个系列我将会继续分析Spring Cloud Alibaba相关组件的源码。Spring Cloud Config 基础使用请移步 《[配置中心Spring Cloud Config](https://blog.csdn.net/u014494148/article/details/117253831)》

前言

今天这篇文章我们来分析一下Spring Cloud Config 配置中心的源码,这应该是Spring Cloud Netflix的源码分析的最后一篇。下一个系列我将会继续分析Spring Cloud Alibaba相关组件的源码。Spring Cloud Config 基础使用请移步 《配置中心Spring Cloud Config

回顾

配置中心的工作流程:微服务 -> 配置中心 -> Git仓库
在这里插入图片描述

简单回顾一下配置中心的搭建流程,首先需要导入spring-cloud-config-server 配置中心依赖,然后启动类需要注解上@EnableConfigServer

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServerApplication1070
{
   
   

    public static void main( String[] args )
    {
   
   
        SpringApplication.run(ConfigServerApplication1070.class);
    }
}

然后使用bootstrap.yml配置Git仓库,比如:

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/little_wolf/springcloud-config.git #配置远程仓库地址,去仓库中复制
          username: 1462163787@qq.com    #仓库是私有的需要账号
          password: 密码

这样的话配中心就自然回去Git仓库下载配置文件,比如我们访问:http://localhost:1070/application-pay-dev.yml ,配置中心就回去Git下载application-pay-dev.yml配置文件,当然前台是Git仓库中是有这个文件的,如:
在这里插入图片描述

@EnableConfigServer注解

该注解的作用是启动config服务端,注解源码如下

/**
 * @author Dave Syer
 * @author
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigServerConfiguration.class)
public @interface EnableConfigServer {
   
   

}

注解上有一个@Import(ConfigServerConfiguration.class) , ConfigServerConfiguration的作用只是作为ConfigServer自动配置的开启条件,如下:

/**
 * @author Spencer Gibb
 */
@Configuration
public class ConfigServerConfiguration {
   
   
    class Marker {
   
   }

    @Bean
    public Marker enableConfigServerMarker() {
   
   
        return new Marker();
    }
}

这个类里面貌似啥都没有做,只是注册了一个叫Marker的Bean而已

ConfigServerAutoConfiguration自动配置

ConfigServerAutoConfiguration是Config的自动配置类,通过SpingBoot自动配置流程注册到Spring容器中【SpringBoot自动配置说过很多次了这里不在赘述了】,它位于spring-cloud-config-server这个jar中
在这里插入图片描述
我们看一下这个自动配置类中有什么

@Configuration
//条件,如果容器总中有 Marker 这个bean,该类就可以起作用
@ConditionalOnBean(ConfigServerConfiguration.Marker.class)
//开启config的配置,ConfigServerProperties用来加载yml中的config配置
@EnableConfigurationProperties(ConfigServerProperties.class)
//导入了一些config相关的配置
@Import({
   
    EnvironmentRepositoryConfiguration.class, CompositeConfiguration.class, ResourceRepositoryConfiguration.class,
        ConfigServerEncryptionConfiguration.class, ConfigServerMvcConfiguration.class })
public class ConfigServerAutoConfiguration {
   
   

}

在ConfigServerAutoConfiguration自动配置类中没有做任何配置,但是该类上大有文章

  • @ConditionalOnBean(ConfigServerConfiguration.Marker.class):这个是一个配置生效的前置条件,如果Sping容器中存在ConfigServerConfiguration.Marker这个B ean,ConfigServerAutoConfiguration配置类才生效,是不是和上面ConfigServerConfiguration联系起来了
  • @EnableConfigurationProperties(ConfigServerProperties.class):ConfigServerProperties是用来加载配置文件中 spring.cloud.config.server开头的配置中心配置项目
  • EnvironmentRepositoryConfiguration:这个是仓库环境配置,比如基于Git的仓库配置,基于本地的仓库配置,基于SVN的仓库配置,基于JDBC的仓库配置,或者多种仓库复合配置。
  • ResourceRepositoryConfiguration:这个是资源仓库配置,其中注册了 ResourceRepository这个Bean,其作用就是用来将载配置文件加载成为Resource对象。
  • ConfigServerEncryptionConfiguration:配置中心的加密配置,该类中注册了一个 EncryptionController 控制器,这个是用来处理针对有加密的配置的请求。
  • ConfigServerMvcConfiguration:配置中心的MVC配置类,其中注册了一个 EnvironmentController ,这个类也是一个控制器,我们向配置中心发起的获取配置文件的请求就是它来处理的,这个我们要重点分析。

    MultipleJGitEnvironmentRepository 配置

    在EnvironmentRepositoryConfiguration配类中有针对于各种仓库的配置

    @Configuration
    @EnableConfigurationProperties({
         
          SvnKitEnvironmentProperties.class,
          JdbcEnvironmentProperties.class, NativeEnvironmentProperties.class, VaultEnvironmentProperties.class })
    @Import({
         
          CompositeRepositoryConfiguration.class, JdbcRepositoryConfiguration.class, VaultRepositoryConfiguration.class,
          SvnRepositoryConfiguration.class, NativeRepositoryConfiguration.class, GitRepositoryConfiguration.class,
          DefaultRepositoryConfiguration.class })
    public class EnvironmentRepositoryConfiguration {
         
         
      ...省略部分代码.......
    
      @Configuration
      @ConditionalOnClass(TransportConfigCallback.class)
      static class JGitFactoryConfig {
         
         
          //基于一个或多个git存储库的EnvironmentRepository 配置工厂
          @Bean
          public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory(
                  ConfigurableEnvironment environment, ConfigServerProperties server,
                  Optional<ConfigurableHttpConnectionFactory> jgitHttpConnectionFactory,
                  Optional<TransportConfigCallback> customTransportConfigCallback) {
         
         
              return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory,
                      customTransportConfigCallback);
          }
      }
      //Http客户端配置,发网络请求的时候用到
     @Configuration
      @ConditionalOnClass({
         
          HttpClient.class, TransportConfigCallback.class })
      static class JGitHttpClientConfig {
         
         
    
          @Bean
          public ConfigurableHttpConnectionFactory httpClientConnectionFactory() {
         
         
              return new HttpClientConfigurableHttpConnectionFactory();
          }
      }
    
      ...省略部分
      代码.......
      //[重要] git的仓库配置
      @Configuration
      @Profile("git")
      class GitRepositoryConfiguration extends DefaultRepositoryConfiguration {
         
         
      }
    
      //svn仓库配置
      @Configuration
      @Profile("subversion")
      class SvnRepositoryConfiguration {
         
         
    
          @Bean
          public SvnKitEnvironmentRepository svnKitEnvironmentRepository(SvnKitEnvironmentProperties environmentProperties,
                                                                         SvnEnvironmentRepositoryFactory factory) {
         
         
              return factory.build(environmentProperties);
          }
      }
    
      //针对于JDBC的仓库配置
    
      @Configuration
      @Profile("jdbc")
      @ConditionalOnClass(JdbcTemplate.class)
      class JdbcRepositoryConfiguration {
         
         
      ...省略部分代码...
    

    我们重点跟一下 GitRepositoryConfiguration 这个针对于Git的仓库配置,因为我们使用的是这个, 该配置类继承了 DefaultRepositoryConfiguration

    @Configuration
    @ConditionalOnMissingBean(value = EnvironmentRepository.class, search = SearchStrategy.CURRENT)
    class DefaultRepositoryConfiguration {
         
         
      @Autowired
      private ConfigurableEnvironment environment;
    
      @Autowired
      private ConfigServerProperties server;
    
      @Autowired(required = false)
      private TransportConfigCallback customTransportConfigCallback;
      //注册MultipleJGitEnvironmentRepository,基于一个或多个git存储库的EnvironmentRepository 
      @Bean
      public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
              MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
              MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
         
         
          return gitEnvironmentRepositoryFactory.build(environmentProperties);
      }
    }
    

    DefaultRepositoryConfiguration中注册了一个 MultipleJGitEnvironmentRepository类,该类实现于 EnvironmentRepository ,基于一个或多个git存储库的EnvironmentRepository ,功能比如:从磁盘加载配置文件,去git克隆一个配置文件等的呢

EnvironmentController 控制器

我们先看一下 ConfigServerMvcConfiguration 配置类的源码

@Configuration
@ConditionalOnWebApplication
public class ConfigServerMvcConfiguration extends WebMvcConfigurerAdapter {
   
   

    @Autowired(required = false)
    private EnvironmentEncryptor environmentEncryptor;

    @Autowired(required = false)
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
   
   
        configurer.mediaType("properties", MediaType.valueOf("text/plain"));
        configurer.mediaType("yml", MediaType.valueOf("text/yaml"));
        configurer.mediaType("yaml", MediaType.valueOf("text/yaml"));
    }
    //【重要】 注册 EnvironmentController
    @Bean
    public EnvironmentController environmentController(EnvironmentRepository envRepository, ConfigServerProperties server) {
   
   
        EnvironmentController controller = new EnvironmentController(encrypted(envRepository, server), this.objectMapper);
        controller.setStripDocumentFromYaml(server.isStripDocumentFromYaml());
        controller.setAcceptEmpty(server.isAcceptEmpty());
        return controller;
    }

    ...省略部分代码...

该配置类中注册了 EnvironmentController ,我们向配置中心发起的加载配置文件的请求 http://localhost:1070/application-pay-dev.yml 就是它来处理的,我们继续看它的源码

@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
   
   

    private EnvironmentRepository repository;
    private ObjectMapper objectMapper;

    private boolean stripDocument = true;
    private boolean acceptEmpty = true;

    public EnvironmentController(EnvironmentRepository repository) {
   
   
        this(repository, new ObjectMapper());
    }

    public EnvironmentController(EnvironmentRepository repository,
            ObjectMapper objectMapper) {
   
   
        this.repository = repository;
        this.objectMapper = objectMapper;
    }

    /**
     * Flag to indicate that YAML documents which are not a map should be stripped of the
     * "document" prefix that is added by Spring (to facilitate conversion to Properties).
     *
     * @param stripDocument the flag to set
     */
    public void setStripDocumentFromYaml(boolean stripDocument) {
   
   
        this.stripDocument = stripDocument;
    }

    /**
     * Flag to indicate that If HTTP 404 needs to be sent if Application is not Found
     *
     * @param acceptEmpty the flag to set
     */
    public void setAcceptEmpty(boolean acceptEmpty) {
   
   
        this.acceptEmpty = acceptEmpty;
    }

    @RequestMapping("/{name}/{profiles:.*[^-].*}")
    public Environment defaultLabel(@PathVariable String name,
            @PathVariable String profiles) {
   
   
        return labelled(name, profiles, null);
    }
    //[重要] 请求    http://xxx:port/application-xx-dev.yml 走的这里
    @RequestMapping("/{name}/{profiles}/{label:.*}")
    public Environment labelled(@PathVariable String name, @PathVariable String profiles,
            @PathVariable String label) {
   
   
        if (name != null && name.contains("(_)")) {
   
   
            // "(_)" is uncommon in a git repo name, but "/" cannot be matched
            // by Spring MVC
            name = name.replace("(_)", "/");
        }
        if (label != null && label.contains("(_)")) {
   
   
            // "(_)" is uncommon in a git branch name, but "/" cannot be matched
            // by Spring MVC
            label = label.replace("(_)", "/");
        }
        Environment environment = this.repository.findOne(name, profiles, label);
        if(!acceptEmpty && (environment == null || environment.getPropertySources().isEmpty())){
   
   
             throw new EnvironmentNotFoundException("Profile Not found");
        }
        return environment;
    }

重点看EnvironmentController#labelled方法,它有两个参数 ,name和profiles,加入请求的配置文件是:http://xxx:port/application-pay-dev.yml 那么

  • name: 就是 application-pay 文件名
  • profiles: 就是 dev 环境名
  • label:分支,如果没有指定就是null,后面分支默认会使用master

该方法接收到请求的文件名,然后做了一些符号的处理,把(_)替换成“/” , 然后调用 EnvironmentRepository.findOne方法加载配置,返回一个Environment对象。

Environment 配置的封装

Environment 对象是对 文件名(name),环境名(profiles),分支(label)的封装。

public class Environment {
   
   
    //文件名
    private String name;
    //环境名 profile
    private String[] profiles = new String[0];
    //分支
    private String label;
    //【重要】加载到的配置内容其实是在PropertySource中
    private List<PropertySource> propertySources = new ArrayList<>();

    private String version;

    private String state;
    ...省略...
}

PropertySource 资源配置

//【重要】用来封装配置内容的资源对象
public class PropertySource {
   
   
    //文件名:如 https://gitee.com/little_wolf/springcloud-config-1107.git/application-pay-dev.yml
    private String name;
    //一个map用来装配置文件中的所有的配置项目
    private Map<?, ?> source;

文章就先到这里把,喜欢的话给个好评哦!!!

相关文章
|
6月前
|
C++
【C++】bind绑定包装器全解(代码演示,例题演示)
【C++】bind绑定包装器全解(代码演示,例题演示)
|
6月前
|
安全 编译器 C++
【C/C++ 基础知识 】 C++ 初始化大全:选择最适合您需求的方式
【C/C++ 基础知识 】 C++ 初始化大全:选择最适合您需求的方式
147 2
|
前端开发 Java 网络架构
「推荐收藏!」【Spring源码探究】(一)MVC容器初始化🏅彻底让你明白运行原理和源码流程
「推荐收藏!」【Spring源码探究】(一)MVC容器初始化🏅彻底让你明白运行原理和源码流程
105 0
|
XML Java 程序员
spring4.1.8初始化源码学习三部曲之三:AbstractApplicationContext.refresh方法
《spring4.1.8初始化源码学习三部曲》系列的终篇,重点是学习AbstractApplicationContext类的refresh()方法
109 0
spring4.1.8初始化源码学习三部曲之三:AbstractApplicationContext.refresh方法
|
消息中间件 XML 运维
Spring源码分析(九)lazy-init 在Spring中是怎么控制加载的
ApplicationContext实现的默认行为就是在启动时将所有singleton bean提前进行实例化(也就是依赖注入)。提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean。通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小时甚至几天)。
|
PHP C#
C#(十五)之C#中的类
本篇内容记录了定义类文件及代码演示。
102 0
C#(十五)之C#中的类
|
PHP C#
C#(十四)之函数(方法)
本篇内容记录了函数、函数的参数、参数匹配、递归函数。
152 0
C#(十四)之函数(方法)
|
XML IDE Java
阅读Spring源码第一步:源码编译与创建调试入口
 Spring开源框架经过很长时间的发展,各个模块均已成熟,一个常识就是一个可靠,可扩展的高性能框架,它的代码行数是相当可观的,我用static插件简略测算了一下,Spring的源码有100多万行,可以想象其中的调用逻辑是相当复杂的,所以将Spring源码下载到本地再编译的话,我们就可以通过IDE的debug来来到抽丝剥茧分析源码的目的,并且我们可以很方便的使用idea来查看调用栈,方法的调用关系也就比较明了了。
阅读Spring源码第一步:源码编译与创建调试入口
|
数据采集 缓存 JavaScript
第一节--项目介绍和初始化
第一节--项目介绍和初始化
89 0
第一节--项目介绍和初始化
|
XML 编解码 缓存
6种常用Bean拷贝工具一览
6种常用Bean拷贝工具一览
609 0
6种常用Bean拷贝工具一览