SpringCloud的动态配置实现原理
前言
动态配置(变动频率高的,如支付方式,页面展示信息)和静态配置(数据库连接配置等)
- 核心注解@RefreshScope
对Bean创建动态代理 - 核心类RefreshScope
负责销毁被@RefreshScope注解配置的Bean - ContextRefresher
负责刷新环境Environment,更新Environment中的所有配置值
Spring Cloud负责更新环境Environment以及创建新的动态配置bean,而判断配置是否改变,以及怎么获取新的配置则是由第三方框架实现的,下面的测试都以 Nacos 为例。
第一种方式
使用@ConfigurationProperties 注解,不需要使用 @RefreshScope注解
myuser:
name: lisi
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "myuser")
public class UserProperties {
private String name;
}
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private UserProperties userProperties;
@Autowired
private ContextRefresher contextRefresher;
@GetMapping("/testRefreshExecute")
@ApiOperation("测试刷新对象执行")
public void testRefreshExceute() {
//这个步骤一般由配置中心框架来触发
contextRefresher.refresh();
}
@GetMapping("/testRefreshQuery")
@ApiOperation("测试刷新对象查询")
public String testRefreshQuery() {
return userProperties.getName();
}
}
测试步骤
- /test/testRefreshQuery获取旧的配置值
- 修改配置中心的值
- Nacos 会通过长连接监听到此修改,调用ContextRefresher的 refresh() 方法,会发送EnvironmentChangeEvent事件,ConfigurationPropertiesRebinder监听此事件并重新绑定属性值(ConfigurationPropertiesBindingPostProcessor)
- /test/testRefreshQuery获取新的配置值
这种方式的原理就是重新走Bean的初始化流程。
第二种方式
使用 @RefreshScope 注解
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Data
@Component
@RefreshScope
public class UserProperties {
@Value("${myuser.name}")
private String name;
}
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private UserProperties userProperties;
@Autowired
private ContextRefresher contextRefresher;
@GetMapping("/testRefreshExecute")
@ApiOperation("测试刷新对象执行")
public void testRefreshExceute() {
//这个步骤一般由配置中心框架来触发
contextRefresher.refresh();
}
@GetMapping("/testRefreshQuery")
@ApiOperation("测试刷新对象查询")
public String testRefreshQuery() {
return userProperties.getName();
}
}
- /test/testRefreshQuery获取旧的配置值
- 修改配置中心的值
- Nacos 会通过长连接监听到此修改,调用ContextRefresher的 refresh() 方法,发送EnvironmentChangeEvent事件,并调用RefreshScope的refreshAll()方法清空Bean缓存。
- /test/testRefreshQuery获取新的配置值
@RefreshScope注解会创建动态代理对象,实际类型为ScopedProxyFactoryBean,每次调用方法都会调用SimpleBeanTargetSource的getTarget()方法,RefreshScope的get(beanName, objectFactory)第一次从IOC容器创建,后续走缓存。
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name,
new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
@Override
public void destroy() {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally {
lock.unlock();
}
}
}
}
this.cache.put()方法底层是putIfAbsent(),所以多次使用的是同一个BeanLifecycleWrapper对象。destroy()方法会清空cache,下次使用的就是新创建的BeanLifecycleWrapper了(重新创建 Bean)。
总结
- NacosContextRefresher 类的 registerNacosListener() 方法会注册一个监听器,发送 RefreshEvent 事件,RefreshEventListener 来处理此事件,内部就是调用 ContextRefresher 的 refresh() 方法。
- 上面两种方式的底层原理都是会重新走 Bean 的初始化过程,在这个过程中就会重新获取配置值。
参考
Spring Cloud动态配置实现原理与源码分析
Nacos共享配置(shared-configs)和扩展配(extension-config)