wayne
wayne
发布于 2025-07-11 / 1 阅读
0
0

spring之我见 - RefreshScope原理 - 从动态配置到代理机制

从 getBean 讲起

getBean 方法是 Spring IOC 的老朋友,在里面有一个细节我们可能会忽略。

Spring IOC 针对不同的 bean 管理模式有不同的执行策略,而我们最常见熟知的就是 单例原型 ,下面在 getBean 的过程中就会首先根据不同的管理策略执行不同的逻辑方法,而我们今天要讲的是比较少走的 else 逻辑部分。

if (mbd.isSingleton()) {

				...

}

else if (mbd.isPrototype()) {

                .....

}

else {

				......

}

单例 的特点是整个生命周期只创建一次对象,而 原型 是每getBean 都会创建新的对象。除此之外Spring 也支持自定义的管理策略。比如这次说的 RefreshScopeRefreshScope 是一个能够在程序运行时可控的触发刷新(原理是销毁重新创建一个对象)的一种 Spring bean 管理策略。而使用场景最多的是项目配置项的及时更新(动态配置),其原理就是销毁老对象,然后重新创建对象的时候对配置进行重新获取,这样就可以实现配置的及时获取。

RefreshScope 的核心机制

@RefreshScope 本质是 @Scope("refresh") 的封装,其 BeanDefinition 的 scope 属性为 refresh,因此会进入 getBeanelse 逻辑:

// 关键代码:获取自定义作用域处理器
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

代理对象的生成

@RefreshScopeproxyMode = TARGET_CLASS 会触发代理创建,该方法会创建一个新的 BeanDefinition ,该 BeanDefinition 的类型ScopedProxyFactoryBean,并且 "scopedTarget." + bean名称 作为 被代理的beanbeanName 。该方法最后返回的是代理类的 BeanDefinition

// 关键步骤:生成 ScopedProxyFactoryBean 的 BeanDefinition
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
    originalDef, registry, true /*proxyTargetClass*/);

也就是说,这个 refresh beanioc容器 里已经不是原始的类,而是一个代理对象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 --> 代理对象下次请求时重建实例


评论