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

Posted 墨家巨子@俏如来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二十四.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();
		}
	}

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

以上是关于二十四.SpringCloudConfig源码-配置拉取流程的主要内容,如果未能解决你的问题,请参考以下文章

jquery源码学习笔记(二十四)

合集DUBBO源码解析及最佳实践合集二十四篇

android源码解析(二十四)-->onSaveInstanceState执行时机

JDK源码(二十四):HashSet

netty入门(二十四)Netty心跳机制源码剖析

Spring源码分析(二十四)初始化非延迟加载单例