SpringCloudConfig 的 工作流程:
当触发热更新的时候,会调用 ContextRefresher 的 refresh()。方法的实现如下:
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
//1
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
//2
addConfigFilesToEnvironment();
//3
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
//4
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
这个方法接收了环境中所有的 PropertySource ,并将其中的非标准属性源
的所有属性汇总到一个 Map 。
这里的标准属性源
如下所示:
StandardEnvironment 会注册系统变量(System Properties
)和环境变量(System Environment
)
StandardServletEnvironment 会注册 Servlet 环境下的 Servlet Context
和 Servlet Config 的初始参数(Init Params
)和JNDI
的属性。
###addConfigFilesToEnvironment方法
addConfigFilesToEnvironment()
的实现逻辑:
ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
//复制得到的 environment 主要包含
//systemProperties、systemEnvironment、refreshArgs和defaultProperties
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
//利用SpringApplicationBuilder,重新加载最新的属性源
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
.environment(environment);
builder.application()
.setListeners(Arrays.asList(new BootstrapApplicationListener(),
new ConfigFileApplicationListener()));
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 {
//忽略
}
return capture;
}
###属性源更新后处理
changes方法
收集发生改变的key集合。publishEvent方法
发送EnvironmentChangeEvent 事件##EnvironmentChangeEvent
EnvironmentChangeEvent 主要触发两个行为:
@ConfigurationProperties
注解的 Spring Bean。logging.level.*
配置发生了改变,重新设置日志级别。这两段逻辑分别可以在 ConfigurationPropertiesRebinder
和 LoggingRebinder
中看到。
###ConfigurationPropertiesRebinder
ConfigurationPropertiesRebinder 类的监控方法onApplicationEvent
很简单,主要就是遍历每个 Bean,destroyBean和initializeBean每个bean。
需要注意的是: ConfigurationPropertiesRebinder的beans字段 只包含@ConfigurationProperties
注解的Bean。因此配置的热更新只会影响 @ConfigurationProperties
注解的Bean.
@ManagedOperation
public void rebind() {
this.errors.clear();
// this.beans 表示的是包含@ConfigurationProperties注解的Bean。
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);
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;
}
###LoggingRebinder
LoggingRebinder 的逻辑很简单,只是调用了 LoggingSystem
的方法重新设置了日志级别.
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.environment == null) {
return;
}
LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader());
setLogLevels(system, this.environment);
}
protected void setLogLevels(LoggingSystem system, Environment environment) {
Map<String, String> levels = Binder.get(environment)
.bind("logging.level", STRING_STRING_MAP)
.orElseGet(Collections::emptyMap);
for (Entry<String, String> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
}
}
private void setLogLevel(LoggingSystem system, Environment environment, String name,
String level) {
try {
if (name.equalsIgnoreCase("root")) {
name = null;
}
level = environment.resolvePlaceholders(level);
system.setLogLevel(name, LogLevel.valueOf(level.toUpperCase()));
}
catch (RuntimeException ex) {
this.logger.error("Cannot set level: " + level + " for '" + name + "'");
}
}
##RefreshScope
public void refreshAll() {
//清空Refresh Scope 中的缓存
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
RefreshScope 是用来存放 scope 类型为 refresh 类型的 Bean(即: 使用RefreshScope注解标识的Bean)。
当一个 Bean 既不是 singleton 也不是 prototype 时,就会从自定义的 Scope 中去获取 Bean ( Spring 允许自定义 Scope ),然后调用Scope的get方法来获取一个实例。
Spring Cloud 扩展了Scope,从而控制了整个 Bean 的生命周期。当配置需要动态刷新的时候, 调用this.scope.refreshAll()这个方法,就会将整个RefreshScope的缓存清空,完成配置可动态刷新的可能。
需要在配置中心中添加 force-pull 信息
spring:
cloud:
config:
server:
git:
# 由于默认将git文件下载到tmp目录,某些os会修改文件,因此需要 强制 pull
force-pull: true
Spring Cloud 是如何实现热更新的