「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
一、前言
前文介绍了 Spring Cloud
的三种配置动态刷新机制,先来回顾下:
- 通过
Environment
获取的配置:例如env.getProperty("book.category")
- 被
@RefreshScope
注解修饰的类:这些scope
值为refresh
的类初始化时经过了特殊处理,当触发RefreshEvent
事件后会重新构造 - 被
@ConfigurationProperties
注解修饰的配置类:这些配置类生效的原因是触发了EnvironmentChangeEvent
事件
Spring Cloud
配置动态刷新机制基于事件监听机制,涉及以下两个事件:
-
RefreshEvent
事件:配置刷新事件。接收到此事件后会构造一个临时的
ApplicationContext
(会加上BootstrapApplicationListener
和ConfigFileApplicationListener
,这意味着从配置中心和配置文件重新获取配置数据)。构造完毕后,新的Environment
里的PropertySource
会跟原先的Environment
里的PropertySource
进行对比并覆盖。 -
EnvironmentChangeEvent
事件:环境变化事件。接收到此事件表示应用里的配置数据已经发生改变。
EnvironmentChangeEvent
事件里维护着一个配置项keys
集合,当配置动态修改后,配置值发生变化后的key
会设置到事件的keys
集合中。
二、@RefreshScope
原理
@RefreshScope
注解的作用:就是使其修饰的类在收到 RefreshEvent
事件的时候被销毁,再次获取这个类的时候会重性构造,重新构造意味着重性解析表达式,这也代表着获取最新的配置。
主要动作:
- 创建:获取类时,构造创建
- 销毁:收到
RefreshEvent
事件时,销毁对象
先来看下 @RefreshScope
的定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh") // Spring Cloud 新增了 scope 值为 refresh 类型的定义
@Documented
public @interface RefreshScope {
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
说到 scope
,先来看下 Spring
的 scope
有哪些:
scope 值 | 作用 |
---|---|
singlon | 全局只有一个实例。每次获取的相同类型的 Bean 都是同一个实例 |
prototype | 每次获取 Bean 都创建一个新的实例 |
request | 同一个 Request 作用域内会返回之前保留的 Bean , 否则重新创建 Bean |
session | 同一个 Session 作用域内会返回之前保留的 Bean ,否则重新创建 Bean |
global session | Portlet 环境下多个 Portlet 共享的 Session 作用域内会返回之前保留的 Bean ,否则重新创建 Bean |
Spring Cloud
新增了 scope
值为 refresh
类型的定义,表示 Bean
支持配置动态刷新:
scope 值 | 作用 |
---|---|
refresh | 支持配置动态刷新 |
(1)创建
这里就不得不提 Spring
创建 Bean
流程:
对应源码:
BeanFactory
->AbstractBeanFactory
->getBean()
->doGetBean()
protected <T> T doGetBean(final String name, final Class<T> requiredType,
final Object[] args, boolean typeCheckOnly)
throws BeansException {
// ... ...
try {
// ... ...
// 创建单例 Bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
// ... ...
}
});
// ... ...
}
// 创建原型 Bean
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
// ... ...
}
// 其他类型 Bean
else {
// 由 @RefreshScope 注解可知:
// 这边就是 scopeName = refresh
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
// ... ...
}
}
// ... ...
}
由此可看出:
- 单例和原型
Bean
是硬编码单独处理 - 其他需要通过
Scope
来处理
对应:RefreshScope refreshScope = scopes.get("refresh", ObjectFactory);
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
......
}
之后就会调用父类 GenericScope.get()
,再之后就是创建对应的 Bean
。
(2)销毁
Spring Cloud
对这个 scope
值为 refresh
的 Bean
做了哪些操作能够使其支持配置动态刷新?
RefreshScope
类实现了BeanDefinitionRegistryPostProcessor
接口:postProcessBeanDefinitionRegistry()
- 对满足
scope
为refresh
条件的BeanDefinition
做了一些修改:把这个Bean
类型修改成LockedScopedProxyFactoryBean
。
对应源码:
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
// 1. 实现接口
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
this.registry = registry;
super.postProcessBeanDefinitionRegistry(registry);
}
// 2. LockedScopedProxyFactoryBean 在父类 GenericScope 中
}
每次 RefreshEvent
事件发送完毕之后,都会触发 RefreshScope
的 refreshAll
方法:
@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
@ManagedOperation(description = "Dispose of the current instance of all beans "
+ "in this scope and force a refresh on next method execution.")
public void refreshAll() {
// 1. 调用父类销毁
super.destroy();
// 2. 发送事件:已刷新
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
}
Tips
:销毁之后,下次获取这些 Bean
的时候会重新构造一遍(意味着会重新解析表达式,这也代表着会获取最新的配置)。
由于 LockedScopedProxyFactoryBean
内部的每个操作都会加锁:
- 因此,调用
ConfigurationController
的event
方法时会获取锁,event
方法内部发送的RefreshEvent
事件会保触发RefreshScope#destroy
方法,destroy
方法内部也会获取同一个锁,这就会出现死锁现象。 - 所以,需要将发送
RefreshEvent
事件的方法移到另外一个没有@RefreshScope
注解的Controller
中。
三、@ConfigurationProperties
原理
为什么加上
@ConfigurationProperties
也能实现配置动态刷新?
用屁股想想,照猫画虎:
- 创建:
Spring
的Bean
机制 - 销毁:接收
EnvironmentChangeEvent
事件,销毁对象
举个栗子,直接监听这个事件:
@Component
class EventReceiver implements ApplicationListener<EnvironmentChangeEvent> {
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
System.out.println("eventReceiver: ");
System.out.println("event: " + event.getKeys());
}
}
修改配置,日志输出如下:
趁胜追击,来看下源码:ConfigurationPropertiesRebinder
EnvironmentChangeEvent
的监听器是由 ConfigurationPropertiesRebinder
实现的,其主要逻辑在 rebind
方法中:
@Component
@ManagedResource
public class ConfigurationPropertiesRebinder
implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
// ... ...
@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) {
// 1. 销毁 Bean
this.applicationContext.getAutowireCapableBeanFactory()
.destroyBean(bean);
// 2. 再初始化 Bean
this.applicationContext.getAutowireCapableBeanFactory()
.initializeBean(bean, name);
return true;
}
}
// ... ...
}
return false;
}
// ... ...
}