SpringCloud config 原理分析

前置说明

源码来自springcloud.G版本
以下说明是个人观点,如有错误,欢迎评论处进行讨论

SpringCloud config

springcloud 提供了分布式配置中心, 支持git, svn, native(文件配置).jdbc等. 现在就springcloud-config,探究以下其配置加载和刷新的原理.

首先springcloud-config 分为服务端和客户端, 那么服务端必然是加载配置(无论来自git还是文件等), 然后提供rest接口可以进行查询; 而客户端的话,也是相似, 启动的时候加载远程的配置, 在运行时支持动态的刷新配置.

那么其中的关键点就是 服务端如何加载配置, 客户端如何加载配置, 客户端如何刷新配置, 那么以下就这个三点进行分开论述.

服务端加载配置

首先,服务端启动时会有个自动配置类叫做ConfigServerAutoConfiguration, 很明显这个类主要是为了导入其他重要的类, 其中我觉得重要的就是ConfigServerMvcConfiguration ,EnvironmentRepositoryConfiguration
接下来先看下ConfigServerMvcConfiguration, 明显也可以发现它有一个EnvironmentController的controller 进行了bean的注册, 其简略代码如下

// 配置中心 服务提供端口
@RestController
@RequestMapping(method = RequestMethod.GET, path = "${spring.cloud.config.server.prefix:}")
public class EnvironmentController {
	private EnvironmentRepository repository;
...

	@RequestMapping("/{name}/{profiles:.*[^-].*}")
	public Environment defaultLabel(@PathVariable String name,
			@PathVariable String profiles) {
		return labelled(name, profiles, null);
	}

	@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 (!this.acceptEmpty
				&& (environment == null || environment.getPropertySources().isEmpty())) {
			throw new EnvironmentNotFoundException("Profile Not found");
		}
		return environment;
	}
...

那么明显呢这个controller 就是提供客户端进行访问配置的接口.
接下来看看EnvironmentRepositoryConfiguration, 这个配置类很明显就是和我们的配置信息源有关系, 很明显能看到git 之类的配置等.
我们随意观察一个配置源 MultipleJGitEnvironmentRepository ,其简略代码如下

public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository {
...
// 拉取最新的git文件
@Override
	public Locations getLocations(String application, String profile, String label) {
		for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
			if (repository.matches(application, profile, label)) {
				for (JGitEnvironmentRepository candidate : getRepositories(repository,
						application, profile, label)) {
					try {
						Environment source = candidate.findOne(application, profile,
								label);
						if (source != null) {
							return candidate.getLocations(application, profile, label);
						}
					}
					catch (Exception e) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug("Cannot retrieve resource locations from "
									+ candidate.getUri() + ", cause: ("
									+ e.getClass().getSimpleName() + ") "
									+ e.getMessage(), e);
						}
						continue;
					}
				}
			}
		}
		JGitEnvironmentRepository candidate = getRepository(this, application, profile,
				label);
		if (candidate == this) {
			return super.getLocations(application, profile, label);
		}
		return candidate.getLocations(application, profile, label);
	}
	
	// 从配置文件读取成抽象的环境实例
	@Override
	public Environment findOne(String application, String profile, String label) {
		for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
			if (repository.matches(application, profile, label)) {
				for (JGitEnvironmentRepository candidate : getRepositories(repository,
						application, profile, label)) {
					try {
						if (label == null) {
							label = candidate.getDefaultLabel();
						}
						Environment source = candidate.findOne(application, profile,
								label);
						if (source != null) {
							return source;
						}
					}
					catch (Exception e) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug(
									"Cannot load configuration from " + candidate.getUri()
											+ ", cause: (" + e.getClass().getSimpleName()
											+ ") " + e.getMessage(),
									e);
						}
						continue;
					}
				}
			}
		}
		JGitEnvironmentRepository candidate = getRepository(this, application, profile,
				label);
		if (label == null) {
			label = candidate.getDefaultLabel();
		}
		if (candidate == this) {
			return super.findOne(application, profile, label);
		}
		return candidate.findOne(application, profile, label);
	}
...

我们明显可以看到这个就是获取git的配置属性的, 到现在的话基本可以知道服务端的运行逻辑了

客户端加载配置

了解服务端的配置后, 基本可以知道客户端肯定在某个时刻进行了远程调用, 那么客户端是在合适进行获取的呢, 这个就涉及springboot 的启动时的事件驱动的.
要想最快知道在什么时候调用最简单的就是在Resttemplate的exchange打个断点, 于是我就发现了是ConfigServicePropertySourceLocator进行了远程配置的调用, 那么这个类是有自动配置类ConfigServiceBootstrapConfiguration 进行配置的, 这个也没啥好说的. 简略代码如下


// 自动配置类
public class PropertySourceBootstrapConfiguration implements
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		CompositePropertySource composite = new CompositePropertySource(
				BOOTSTRAP_PROPERTY_SOURCE_NAME);
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		boolean empty = true;
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			PropertySource<?> source = null;
			// 调用对应的 获取远程的配置的执行器
			source = locator.locate(environment);
			if (source == null) {
				continue;
			}
			logger.info("Located property source: " + source);
			composite.addPropertySource(source);
			empty = false;
		}
		if (!empty) {
			MutablePropertySources propertySources = environment.getPropertySources();
			String logConfig = environment.resolvePlaceholders("${logging.config:}");
			LogFile logFile = LogFile.get(environment);
			if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
				propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
			}
			// 将获取到的配置加入的当前上下文的环境中去
			insertPropertySources(propertySources, composite);
			reinitializeLoggingSystem(environment, logConfig, logFile);
			setLogLevels(applicationContext, environment);
			handleIncludedProfiles(environment);
		}
	}
...
}

// 远程配置获取
public class ConfigServicePropertySourceLocator implements PropertySourceLocator {
@Override
	@Retryable(interceptor = "configServerRetryInterceptor")
	public org.springframework.core.env.PropertySource<?> locate(
			org.springframework.core.env.Environment environment) {
		ConfigClientProperties properties = this.defaultProperties.override(environment);
		CompositePropertySource composite = new CompositePropertySource("configService");
		RestTemplate restTemplate = this.restTemplate == null
				? getSecureRestTemplate(properties) : this.restTemplate;
		Exception error = null;
		String errorBody = null;
		try {
			String[] labels = new String[] { "" };
			if (StringUtils.hasText(properties.getLabel())) {
				labels = StringUtils
						.commaDelimitedListToStringArray(properties.getLabel());
			}
			String state = ConfigClientStateHolder.getState();
			// Try all the labels until one works
			for (String label : labels) {
			// 获取远程的配置
				Environment result = getRemoteEnvironment(restTemplate, properties,
						label.trim(), state);
				if (result != null) {
					log(result);

					if (result.getPropertySources() != null) { // result.getPropertySources()
																// can be null if using
																// xml
						for (PropertySource source : result.getPropertySources()) {
							@SuppressWarnings("unchecked")
							Map<String, Object> map = (Map<String, Object>) source
									.getSource();
							composite.addPropertySource(
									new MapPropertySource(source.getName(), map));
						}
					}

					if (StringUtils.hasText(result.getState())
							|| StringUtils.hasText(result.getVersion())) {
						HashMap<String, Object> map = new HashMap<>();
						putValue(map, "config.client.state", result.getState());
						putValue(map, "config.client.version", result.getVersion());
						composite.addFirstPropertySource(
								new MapPropertySource("configClient", map));
					}
					return composite;
				}
			}
		}
		catch (HttpServerErrorException e) {
			error = e;
			if (MediaType.APPLICATION_JSON
					.includes(e.getResponseHeaders().getContentType())) {
				errorBody = e.getResponseBodyAsString();
			}
		}
...
		return null;

	}
...
}


至此的话,客户端的远程配置获取基本已经讲完了, 接下来的客户端配置的刷新

客户端配置刷新

其实客户端配置刷新的逻辑也比较简单, 主要涉及到一下几个类

  1. RefreshEndpoint 提供刷新的端口, 也就是 /refresh
  2. RefreshEventListener 对 RefreshEvent 事件监听来刷新配置 这个是给 springcloud-bus 使用的, 当然我们也可以发布事件
  3. ContextRefresher 刷新配置的协调者, 进行环境的更新以及 refreshScope 的刷新
  4. ConfigurationPropertiesRebinder 监听EnvironmentChangeEvent事件, 重新绑定ConfigurationProperties
  5. RefreshScope 用于destory所有的被@RefreshScope注解的bean, 然后发布相关事件

基本上客户端刷新的要点都在上面的几个类中了, 具体介绍一下几个重要的方法

public class ContextRefresher {

//刷新配置
public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}

// 刷新环境
public synchronized Set<String> refreshEnvironment() {
		Map<String, Object> before = extract(
				this.context.getEnvironment().getPropertySources());
				// 获取远程的配置
		addConfigFilesToEnvironment();
		Set<String> keys = changes(before,
				extract(this.context.getEnvironment().getPropertySources())).keySet();
				// 发布一个环境刷新的事件, 主要是给ConfigurationPropertiesRebinder 进行重新绑定
		this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
		return keys;
	}


// 获取远程的配置
	ConfigurableApplicationContext addConfigFilesToEnvironment() {
		ConfigurableApplicationContext capture = null;
		try {
			StandardEnvironment environment = copyEnvironment(
					this.context.getEnvironment());
			SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
					.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
					.environment(environment);
			builder.application()
					.setListeners(Arrays.asList(new BootstrapApplicationListener(),
							new ConfigFileApplicationListener()));
							// 这里模仿启动的时的样子, 通过BootstrapApplicationListener进行获取最新的配置
			capture = builder.run();
			if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
				environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
			}
			MutablePropertySources target = this.context.getEnvironment()
					.getPropertySources();
			String targetName = null;
			for (PropertySource<?> source : environment.getPropertySources()) {
				String name = source.getName();
				if (target.contains(name)) {
					targetName = name;
				}
				if (!this.standardSources.contains(name)) {
					if (target.contains(name)) {
						target.replace(name, source);
					}
					else {
						if (targetName != null) {
							target.addAfter(targetName, source);
						}
						else {
							// targetName was null so we are at the start of the list
							target.addFirst(source);
							targetName = name;
						}
					}
				}
			}
		}
		finally {
			ConfigurableApplicationContext closeable = capture;
			while (closeable != null) {
				try {
					closeable.close();
				}
				catch (Exception e) {
					// Ignore;
				}
				if (closeable.getParent() instanceof ConfigurableApplicationContext) {
					closeable = (ConfigurableApplicationContext) closeable.getParent();
				}
				else {
					break;
				}
			}
		}
		return capture;
	}
...
}


// 监听EnvironmentChangeEvent事件然后对@ConfigurationProperties 的重新绑定
public class ConfigurationPropertiesRebinder
		implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {

	@ManagedOperation
	public void rebind() {
		this.errors.clear();
		for (String name : this.beans.getBeanNames()) {
			rebind(name);
		}
	}

	@ManagedOperation
	public boolean rebind(String name) {
		if (!this.beans.getBeanNames().contains(name)) {
			return false;
		}
		if (this.applicationContext != null) {
			try {
				Object bean = this.applicationContext.getBean(name);
				if (AopUtils.isAopProxy(bean)) {
					bean = ProxyUtils.getTargetObject(bean);
				}
				if (bean != null) {
					this.applicationContext.getAutowireCapableBeanFactory()
							.destroyBean(bean);
//重新调用initializeBean 方法, 进行属性的绑定, 具体的不进行介绍
					this.applicationContext.getAutowireCapableBeanFactory()
							.initializeBean(bean, name);
					return true;
				}
			}
			catch (RuntimeException e) {
				this.errors.put(name, e);
				throw e;
			}
			catch (Exception e) {
				this.errors.put(name, e);
				throw new IllegalStateException("Cannot rebind to " + name, e);
			}
		}
		return false;
	}


	@Override
	public void onApplicationEvent(EnvironmentChangeEvent event) {
		if (this.applicationContext.equals(event.getSource())
				// Backwards compatible
				|| event.getKeys().equals(event.getSource())) {
			rebind();
		}
	}
...
}


// 这个类也很简单, 主要就是对有这个注解的bean 进行注销
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware,
		ApplicationListener<ContextRefreshedEvent>, Ordered {


	@ManagedOperation(description = "Dispose of the current instance of bean name "
			+ "provided and force a refresh on next method execution.")
	public boolean refresh(String name) {
		if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
			// User wants to refresh the bean with this name but that isn't the one in the
			// cache...
			name = SCOPED_TARGET_PREFIX + name;
		}
		// 注销bean
		if (super.destroy(name)) {
			this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
			return true;
		}
		return false;
	}

	@ManagedOperation(description = "Dispose of the current instance of all beans "
			+ "in this scope and force a refresh on next method execution.")
	public void refreshAll() {
		super.destroy();
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}


结尾

到此为止, 开篇的三个点都讲完了, 基本上springcloud 的配置理的比较清楚了, 也是能看出springcloud强大的架构能力, 设计非常多的有意思的代码: 包括事件驱动的运用, 以及在拉取远程配置时通过模拟启动的SpringApplication进行获取, 也是非常的奇技淫巧, 最后给上一副交互图

架构图

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值