需要热加载的bean需要加上@RefreshScope注解,当配置发生变更的时候可以在不重启应用的前提下完成bean中相关属性的刷新。
经由@RefreshScope修饰的bean将会被RefreshScope代理,其关于bean生命周期的相关方法也在此定义。
@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());
}
RefreshScope代理的bean强制为懒加载,只有在第一次使用的时候才会生成实例,当其需要刷新配置的时候直接调用destory()方法销毁当前bean,这样在刷新配置后在需要生成的bean已经是根据新的配置信息生成,完成bean的热加载。
在spring cloud中,当配置发生变更,将会通过soring cloud bus发送RefreshRemoteApplicationEvent事件给相关应用,在RefreshListener中,开始对于配置的刷新。
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
Set<String> keys = this.contextRefresher.refresh();
log.info("Received remote refresh request. Keys refreshed " + keys);
}
最终刷新将在refreshContext的refresh()方法中进行。
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();
this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
return keys;
}
在这里的刷新一共被分为两个步骤,首先获取新的配置值与老的配置值进行比对并更新,之后再销毁所有被RefreshScope代理的bean。
在第一步,重点在于获取新的配置值,具体实现在addConfigFilesToEnvironment()方法中。
StandardEnvironment environment = copyEnvironment(
this.context.getEnvironment());
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
.environment(environment);
// Just the listeners that affect the environment (e.g. excluding logging
// listener because it has side effects)
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;
}
}
}
}
此处将会以当前的应用的envionment深克隆一份,并根据复制出来的为依据重新建立一个spring boot application,在新的应用建立过程中,将会重新获取配置值,此处也将获取新的配置值,在新的副本应用建立完毕之后,将会返回其配置值准备与旧的配置值进行比对并更新。