从 getBean 讲起
getBean
方法是 Spring IOC
的老朋友,在里面有一个细节我们可能会忽略。
Spring IOC
针对不同的 bean
管理模式有不同的执行策略,而我们最常见熟知的就是 单例
和 原型
,下面在 getBean
的过程中就会首先根据不同的管理策略执行不同的逻辑方法,而我们今天要讲的是比较少走的 else
逻辑部分。
if (mbd.isSingleton()) {
...
}
else if (mbd.isPrototype()) {
.....
}
else {
......
}
单例
的特点是整个生命周期只创建一次对象,而 原型
是每getBean
都会创建新的对象。除此之外Spring
也支持自定义的管理策略。比如这次说的 RefreshScope
, RefreshScope
是一个能够在程序运行时可控的触发刷新(原理是销毁重新创建一个对象)的一种 Spring bean
管理策略。而使用场景最多的是项目配置项的及时更新(动态配置),其原理就是销毁老对象,然后重新创建对象的时候对配置进行重新获取,这样就可以实现配置的及时获取。
RefreshScope 的核心机制
@RefreshScope
本质是 @Scope("refresh")
的封装,其 BeanDefinition 的 scope
属性为 refresh
,因此会进入 getBean
的 else
逻辑:
// 关键代码:获取自定义作用域处理器
Scope scope = this.scopes.get("refresh");
Object bean = scope.get(beanName, () -> createBean(beanName, mbd, args));
get() 方法与缓存设计
当调用 scope.get(beanName, objectFactory)
时,核心流程如下:
// GenericScope 的实现
public Object get(String name, ObjectFactory<?> objectFactory) {
// 1. 从缓存获取包装对象
BeanLifecycleWrapper wrapper = this.cache.putIfAbsent(name,
new BeanLifecycleWrapper(name, objectFactory));
// 2. 若实例不存在或已过期
if (wrapper.getBean() == null) {
synchronized (wrapper) {
if (wrapper.getBean() == null) {
// 3. 通过 createBean() 创建新实例
wrapper.setBean(objectFactory.getObject());
}
}
}
// 4. 返回实际Bean实例(非Wrapper)
return wrapper.getBean();
}
销毁
// RefreshScope 的方法
public void refreshAll() {
super.destroy(); // 调用 GenericScope.destroy()
}
// GenericScope 的实现
public void destroy() {
List<DisposableBean> wrappers = new ArrayList<>();
// 1. 遍历所有 Wrapper
for (BeanLifecycleWrapper wrapper : cache.values()) {
// 2. 提取可销毁的Bean(实现DisposableBean接口的)
Object bean = wrapper.getBean();
if (bean instanceof DisposableBean) {
wrappers.add((DisposableBean) bean);
}
}
// 3. 清空缓存(下次get将触发重建)
cache.clear();
// 4. 同步销毁Bean实例
for (DisposableBean disposable : wrappers) {
disposable.destroy();
}
}
总结核心设计
缓存机制:通过
BeanLifecycleWrapper
包装实例,缓存于ConcurrentHashMap
销毁触发:调用
refreshAll()
清空缓存,下次访问时重建实例
动态配置的桥梁:ContextRefresher
讲到这里,貌似 只提到了 refresh bean
会被销毁重建,但是怎么触发销毁和重建以实现动态配置,可能还是一头雾水。要介绍一个新组件ContextRefresher
,Spring Cloud 通过 ContextRefresher
实现配置刷新闭环:
ContextRefresher
基本原理就是拿到最新的 Environment
,也就是最新的配置信息,然后跟老的 Environment
配置做比对,最后触发 RefreshScope.refreshAll
,销毁所有 RefreshScope Bean
。
public Set<String> refresh() {
Set<String> changedKeys = refreshEnvironment(); // 1. 比对配置差异
scope.refreshAll(); // 2. 销毁所有 RefreshScope Bean
return changedKeys; // 3. 返回变更的配置项
}
public synchronized Set<String> refreshEnvironment() {
// 拿到当前上下文的配置项
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
// 更新环境,LegacyContextRefresher的实现是重新创建一个SpringBoot环境(详看源码)。此时会拿到最新的environment。也就相当于拿到最新的配置。
updateEnvironment();
//对比新老environment的配置项信息
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
//发送事件,监听的对象可以拿到更改的信息项
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
//返回修改的配置项
return keys;
}
使用 Spring Cloud Config
的同学知道可以调用 /refresh
接口刷新配置项,而 /refresh
接口层层拨开其实就是调用 contextRefresher.refresh()
。而事件广播机制可以让其它组件拿到最新的配置信息加以利用。
依赖注入的魔法:代理机制(TargetSource)
到这里,我们知道一个RefreshScope Bean
的整个生命周期是怎么样的,但是还有最后一个问题,RefreshScope Bean
会被销毁重建,那么依赖这些RefreshScope Bean
的对象怎么拿到最新的对象呢?
其实不需要拿到最新的对象,因为一开始的对象并不是RefreshScope Bean
,而是固定的代理类,代理类里面做了些手脚可以随时拿到最新的RefreshScope Bean
。
代理对象的生成
@RefreshScope
的 proxyMode = TARGET_CLASS
会触发代理创建,该方法会创建一个新的 BeanDefinition
,该 BeanDefinition
的类型ScopedProxyFactoryBean
,并且 "scopedTarget." + bean名称
作为 被代理的bean
的beanName
。该方法最后返回的是代理类的 BeanDefinition
。
// 关键步骤:生成 ScopedProxyFactoryBean 的 BeanDefinition
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
originalDef, registry, true /*proxyTargetClass*/);
也就是说,这个 refresh bean
在 ioc容器
里已经不是原始的类,而是一个代理对象ScopedProxyFactoryBean
。
动态目标源(TargetSource)
代理通过 SimpleBeanTargetSource
实现动态实例获取:
public Object getTarget() throws Exception {
return getBeanFactory().getBean(getTargetBeanName()); // 每次调用实时获取最新 Bean
}
代理调用链
调用方 → ScopedProxy → TargetSource.getTarget() → getBean() → 实际实例
总结
这篇比较系统的总结了RefreshScope
, 一开始介绍了 RefreshScope
本身的特性 – 在一定时机下会销毁并重新创建 refresh bean
,那这个时机是什么?就是使用场景最多的动态配置功能
,比如 springcloudconfig,nacos,apollo等,然后带出ContextRefresher
可以触发拿取最新 Environment
并比对出更改的配置 key
并销毁refresh bean
。最后解释了一下为什么重复销毁并创建的 refresh bean
能在依赖注入中更新自己的实体 – 依赖于 代理机制
(TargetSource可以动态每次从getbean方法拿取最新对象)+ 事件广播
(其它组件可以根据返回的修改key做业务逻辑)机制实现每次对象调用都拿取最新的对象。
配置变更 --> ContextRefresher --> 销毁 RefreshScope Bean --> 代理对象下次请求时重建实例