二十四.SpringCloudConfig源码-配置拉取流程

简介: 这篇文章是接上一篇的,因为文章太长看起来太累,所以就分了一下

前言

这篇文章是接上一篇的,因为文章太长看起来太累,所以就分了一下

EnvironmentRepository.findOne 查找配置

上回说到 EnvironmentController 控制器 ,重点关注 labelled 方法, 我打了一个断点跟了一下,代码会走到了 EnvironmentEncryptorEnvironmentRepository#findOne方法中,源码如下:

@Override
public Environment findOne(String name, String profiles, String label) {
   
    //调用了SearchPathCompositeEnvironmentRepository#findOne
    Environment environment = this.delegate.findOne(name, profiles, label);
    if (this.environmentEncryptor != null) {
   
        //解密
        environment = this.environmentEncryptor.decrypt(environment);
    }
    if (!this.overrides.isEmpty()) {
   
        environment.addFirst(new PropertySource("overrides", this.overrides));
    }
    return environment;
}

通过断点看到,在EnvironmentEncryptorEnvironmentRepository#findOne方法中,调用了SearchPathCompositeEnvironmentRepository#findOne方法,SearchPathCompositeEnvironmentRepository 是一个可以通过SearchPathLocator从文件系统中加载配置文件的EnvironmentRepository

SearchPathCompositeEnvironmentRepository并没有findOne方法,它使用的父类的方法CompositeEnvironmentRepository#findOne,最终得到一个Environment 对象。然后通过 EnvironmentEncryptor 进行解密。

进行往下走流程,代码来到 CompositeEnvironmentRepository#findOne

public class CompositeEnvironmentRepository implements EnvironmentRepository {
   
    //仓库列表
    protected List<EnvironmentRepository> environmentRepositories;
    @Override
    public Environment findOne(String application, String profile, String label) {
   
        //Environment 是对配置文件名,环境,分支等封装对象
        Environment env = new Environment(application, new String[]{
   profile}, label, null, null);
        //是不是只配置了一个仓库
        if(environmentRepositories.size() == 1) {
   
            //这里调用的是MultipleJGitEnvironmentRepository#findOne
            Environment envRepo = environmentRepositories.get(0).findOne(application, profile, label);
            //把加载到的配置文件的内容设置给Environment 
            env.addAll(envRepo.getPropertySources());
            //版本号
            env.setVersion(envRepo.getVersion());
            //状态
            env.setState(envRepo.getState());
        } else {
   
            //如果配置了多个仓库,循环通过repo.findOne去下载配置
            for (EnvironmentRepository repo : environmentRepositories) {
   
                env.addAll(repo.findOne(application, profile, label).getPropertySources());
            }
        }
        return env;
}

这里把配置文件名,环境名,分支名封装成Environment 对象,同时该对象也用来接收结果。然后判断了一下是否配置了多个仓库,多个仓库就循环调用findOne方法加载配置,我们这里只配置了一个仓库,走的是MultipleJGitEnvironmentRepository的findOne方法,然后把结果设置给Environment 并返回Environment。

注意:env.addAll(envRepo.getPropertySources()); 这行代码,PropertySources就是加载到的配置文件的内容了。

继续往后面走,代码来到 MultipleJGitEnvironmentRepository#findOne 方法中

@Override
    public Environment findOne(String application, String profile, String label) {
   

        ...省略...
        //这个getRepository方法中对url中的类似{application}的占位符做了一些替换
        JGitEnvironmentRepository candidate = getRepository(this, application, profile,
                label);
        if (label == null) {
   
            //如果label是null,默认使用master作为分支
            label = candidate.getDefaultLabel();
        }
        if (candidate == this) {
   
            //默认走这
            return super.findOne(application, profile, label);
        }
        return candidate.findOne(application, profile, label);
    }

该方法中对URL中的占位符如:{application},{label}等进行替换,然后判断如果label是空的,就使用master作为默认label。最后调用super.findOne方法,此时代码来到其父类 AbstractScmEnvironmentRepository#findOne 方法

@Override
public synchronized Environment findOne(String application, String profile, String label) {
   
    //加载本机的环境存储库
    NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
            new NativeEnvironmentProperties());
    //加载Locations ,Locations是对本地配置文件的封装
    Locations locations = getLocations(application, profile, label);
    //locations.getLocations()得到本地配置文件路径
    delegate.setSearchLocations(locations.getLocations());
    //调用 NativeEnvironmentRepository #findOne方法加载配置
    Environment result = delegate.findOne(application, profile, "");
    //把版本号和label设置给result
    result.setVersion(locations.getVersion());
    result.setLabel(label);
    //执行本地仓库的清理工作
    return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
            getUri());
}

该方法做了这么几件事情:

  1. 把ConfigurableEnvironment交给一个NativeEnvironmentRepository对象,ConfigurableEnvironment中有当前配置中心微服务的原始配置。
  2. 调用 getLocations 方法得到Locations ,它封装了配置文件的 application,profile,label,version ,和本地存储远程拉取下来的配置文件的位置。如:file:/C:/Users/whale/AppData/Local/Temp/config-repo-8104345609176998816/
  3. 把配置文件的地址交给NativeEnvironmentRepository ,然后调用其findOne方法
  4. 最后执行clean清理

getLocations 加载本地配置

我们先看一下 getLocations 方法是怎么加载本地配置文件的,代码来到JGitEnvironmentRepository#getLocations

@Override
public synchronized Locations getLocations(String application, String profile,
        String label) {
   
    if (label == null) {
   
    //默认使用master分支
        label = this.defaultLabel;
    }
    //刷新配置,这个代码回去远程拉取最新的配置文件
    String version = refresh(label);
    //调用 getSearchLocations 处理一下本地配置文件地址,把结果封装成Locations
    return new Locations(application, profile, label, version,
            getSearchLocations(getWorkingDirectory(), application, profile, label));
}

这里调用了 refresh方法下载配置文件,然后调用 getSearchLocations 找到本地下载的配置文件地址,封装成Locations返回。

重点看一下 refresh方法,代码来到JGitEnvironmentRepository#refresh方法中


    /**准备工作目录
     * Get the working directory ready.
     */
    public String refresh(String label) {
   
        Git git = null;
        try {
   
            //【1】创建Git客户端
            git = createGitClient();
            //【2】判断是否要从远程拉取配置文件
            if (shouldPull(git)) {
   
            //【3】执行配置文件拉取动作
                FetchResult fetchStatus = fetch(git, label);
                if (deleteUntrackedBranches && fetchStatus != null) {
   
                    deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
                }
                // checkout after fetch so we can get any new branches, tags, ect.
                //【4】执行checkout操作,切到master分支
                checkout(git, label);
                //判断仓库的所有分支是否包含当前分支
                if (isBranch(git, label)) {
   
                    // merge results from fetch
                    //【5】执行merge操作
                    merge(git, label);
                    if (!isClean(git, label)) {
   
                        logger.warn("The local repository is dirty or ahead of origin. Resetting"
                                + " it to origin/" + label + ".");
                        resetHard(git, label, LOCAL_BRANCH_REF_PREFIX + label);
                    }
                }
            }
            else {
   
                // nothing to update so just checkout
                checkout(git, label);
            }
            //【6】始终返回当前HEAD作为版本 , 把版本号返回
            // always return what is currently HEAD as the version
            return git.getRepository().findRef("HEAD").getObjectId().getName();
        }
        ...省略...
    }
    //创建Git客户端
    private Git createGitClient() throws IOException, GitAPIException {
   
        //工作目录加锁
        File lock = new File(getWorkingDirectory(), ".git/index.lock");
        //锁是否存在
        if (lock.exists()) {
   
            // The only way this can happen is if another JVM (e.g. one that
            // crashed earlier) created the lock. We can attempt to recover by
            // wiping the slate clean.
            logger.info("Deleting stale JGit lock file at " + lock);
            //删除锁
            lock.delete();
        }
        //工作中的配置文件是否创造
        if (new File(getWorkingDirectory(), ".git").exists()) {
   
            //打开Git仓库
            return openGitRepository();
        }
        else {
   
        //拷贝Git仓库
            return copyRepository();
        }
    }

JGitEnvironmentRepository#refresh方法挺复杂的,简单点锁就是根据URL把Git仓库中的配置文件fetch到本地,然后进行checkout,merge等等。然后把本地配置文件的地址。

代码还要回到 .AbstractScmEnvironmentRepository#findOne ,刚才跟的是getLocations方法

@Override
    public synchronized Environment findOne(String application, String profile, String label) {
   
        NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
                new NativeEnvironmentProperties());
        //查找配置的地址
        Locations locations = getLocations(application, profile, label);
        delegate.setSearchLocations(locations.getLocations());
        //把locaitons变成 Environment对象
        Environment result = delegate.findOne(application, profile, "");
        result.setVersion(locations.getVersion());
        result.setLabel(label);
        return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
                getUri());
    }

封装Environment

现在需要跟一下 delegate.findOne方法了,这里调用的是NativeEnvironmentRepository#findOne ,它主要是把配置封装成Environment 对象。

@Override
    public Environment findOne(String config, String profile, String label) {
   
        SpringApplicationBuilder builder = new SpringApplicationBuilder(
                PropertyPlaceholderAutoConfiguration.class);
        //本机环境
        ConfigurableEnvironment environment = getEnvironment(profile);
        builder.environment(environment);
        builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
        if (!logger.isDebugEnabled()) {
   
            // Make the mini-application startup less verbose
            builder.logStartupInfo(false);
        }
        //args中包括 application , profile , label, 配置文件地址 
        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 {
   
        //clean方法会返回一个封装好配置项的Environment 
            return clean(new PassthruEnvironmentRepository(environment).findOne(config,
                    profile, label));
        }
        finally {
   
            context.close();
        }
    }

文章结束,喜欢的话给个好评吧!!!

相关文章
|
5月前
|
SQL 关系型数据库 MySQL
【Seata1.5.2 下载 & 配置 & 整合 & 踩坑 & 测试】—— 含各种踩坑记录(详细版)(上)
【Seata1.5.2 下载 & 配置 & 整合 & 踩坑 & 测试】—— 含各种踩坑记录(详细版)
206 0
|
5月前
|
SQL Java 数据库
【Seata1.5.2 下载 & 配置 & 整合 & 踩坑 & 测试】—— 含各种踩坑记录(详细版)(下)
【Seata1.5.2 下载 & 配置 & 整合 & 踩坑 & 测试】—— 含各种踩坑记录(详细版)(下)
108 0
|
7月前
|
数据处理 数据安全/隐私保护
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)(二)
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)
259 0
|
7月前
|
存储
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)(三)
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)
110 0
|
7月前
|
XML Java 数据库
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)(一)
Flowable:关于流程部署、启动、处理、完成各模块的浅析(图解)
126 0
|
12月前
|
SQL JSON 机器人
pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程
pytest+yaml设计接口自动化框架过程记录(一步一步记录如何设计,完结撒花),源码提供,视频教程
|
12月前
|
Kubernetes 前端开发 Go
上篇:带你手工体验从写代码、编译、打包镜像、部署到K8S的全过程
上篇:带你手工体验从写代码、编译、打包镜像、部署到K8S的全过程
321 0
|
Kubernetes API 数据安全/隐私保护
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
你有没有想过, 如果要在 kubernetes 集群中 **发布** 一个最基本的 **无状态服务**, 并 **提供** 给用户访问, 最少需要配置几个 `K8S Config API` ? 自己写一个, 提升自己。
181 0
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
|
消息中间件 Java RocketMQ
拉取信息的流程小结|学习笔记
快速学习拉取信息的流程小结
78 0
拉取信息的流程小结|学习笔记
|
Java 容器
【SpringBoot 2】(五)自动配置简析源码 开发中小技巧(一)
【SpringBoot 2】(五)自动配置简析源码 开发中小技巧(一)
【SpringBoot 2】(五)自动配置简析源码 开发中小技巧(一)