nacos动态刷新配置原理

Nacos动态配置刷新

环境

springboot 3.0.5
springCloud 2022.0.3
springCloudAlibaba 2022.0.0.0-RC2

探索

共有两种方式来获取nacos的配置

  1. @ConfigurationProperties

  2. @Value+@RefreshScope

配置类形式

原理:通过监听nacos端的配置动态变化

使用springboot的事件监听机制

监听器如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationReadyEvent) {
handle((ApplicationReadyEvent) event);
}
else if (event instanceof RefreshEvent) {
// 刷新事件
handle((RefreshEvent) event);
}
}

public void handle(RefreshEvent event) {
if (this.ready.get()) { // don't handle events before app is ready
log.debug("Event received " + event.getEventDesc());
// 进行刷新
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}

跳转到ContextRefresher类中

1
2
3
4
5
6
7
8
9
public synchronized Set<String> refreshEnvironment() {
// 获取之前的配置
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
// 加载配置文件到ioc容器中?
updateEnvironment();
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}

下面是更新配置的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@Override
protected void updateEnvironment() {
addConfigFilesToEnvironment();
}

/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {
ConfigurableApplicationContext capture = null;
try {
// 当前的
StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());

Map<String, Object> map = new HashMap<>();
map.put("spring.jmx.enabled", false);
map.put("spring.main.sources", "");
// gh-678 without this apps with this property set to REACTIVE or SERVLET fail
map.put("spring.main.web-application-type", "NONE");
map.put(BOOTSTRAP_ENABLED_PROPERTY, Boolean.TRUE.toString());
environment.getPropertySources().addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map));
// 又创造了一个ioc容器???
SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.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 BootstrapConfigFileApplicationListener()));
capture = builder.run();
if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySources target = getContext().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);
// update targetName to preserve ordering
targetName = name;
}
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;
}

更新配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private boolean rebind(String name, ApplicationContext appContext) {
try {
Object bean = appContext.getBean(name);
if (AopUtils.isAopProxy(bean)) {
bean = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// TODO: determine a more general approach to fix this.
// see
// https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
return false; // ignore
}
// 下面是核心代码 直接把Bean给删了
appContext.getAutowireCapableBeanFactory().destroyBean(bean);
appContext.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;
}

Value形式

原理:改变Bean实例化的方式,通过spring广播机制把Bean删除之后再重新实例化

Spring框架真是神奇,Bean的操作基本都可以进行定制

1
2
3
4
@RefreshScope
public class TestController {
@Value("${fang.test}")
private String configValue;
  1. 在Spring Ioc容器中,不走寻常的创建Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
}

ContextRefresher直接对Bean的缓存管理器进行清除

image.png

总结

从这一个小功能能看出cloud基本都是基于boot

而配置动态更新这一功能,更是将Ioc容器玩出花来了

属实是springboot中Bean的最佳应用了


nacos动态刷新配置原理
https://hexo-blog-five-swart.vercel.app/2024/09/11/nacos动态刷新配置原理/
作者
方立
发布于
2024年9月11日
许可协议