SpringBoot+Nacos:@RefreshScope自动刷新原理

@RefreshScope的作用

经过@RefreshScope注解修饰的bean,将被RefreshScope进行代理,用来实现配置、实例热加载,即当配置变更时可以在不重启应用的前提下刷新bean中相关的属性值。

@RefreshScope注解

@RefreshScope的实现如下,非常简单,最主要是@Scope("refresh")和ScopedProxyMode.TARGET_CLASS,表示@RefreshScope 是scopeName="refresh"的 @Scope,且代理模式为TARGET_CLASS。所以要搞懂什么是@RefreshScope,必须先了解@Scope。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

@Scope注解

从上面可以看出,@RefreshScope是是一个符合注解,基于@Scope实现的,@Scope是spring ioc容器的作用域。

在 Spring IoC 容器中具有以下几种作用域:

  • singleton:单例模式(默认),全局有且仅有一个实例
  • prototype:原型模式,每次获取Bean的时候会有一个新的实例
  • request:request表示针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
  • session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
  • global session:global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义

以下是@Scope注解的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
    // scopeName等价于value
    @AliasFor("scopeName")
    String value() default "";
 
    // singleton(单例默认)、prototype、request、session、Global session、refresh
    @AliasFor("value")
    String scopeName() default "";
 
    // DEFAULT :默认,不使用代理
    // NO:不使用代理,等价于DEFAULT
    // INTERFACES:使用基于JDK dynamic proxy实现代理
    // TARGET_CLASS:使用基于cglib实现代理
    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
  }

在spring bean的生命周期管理中,所有经过@Scope注解装饰过的Bean实例都交由Scope自己创建,例如SessionScope是从Session中获取实例的,ThreadScope是从ThreadLocal中获取的,而RefreshScope是在内建缓存中获取的。

AbstractBeanFactory#doGetBean

    protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
                ...
                if (mbd.isSingleton()) {
                    ...
                  //如果为单例(默认),则创建单例Bean
                } else if (mbd.isPrototype()) {
                    ...
                  //如果为prototype,则创建原型Bean
                } else {
                  //如果是Scope Bean,则交由对应Scope自己创建,如refresh则由RefreshScope创建
                    String scopeName = mbd.getScope();
                    Scope scope = (Scope)this.scopes.get(scopeName);
                    ...
                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            this.beforePrototypeCreation(beanName);
                            try {
                                return this.createBean(beanName, mbd, args);
                            } finally {
                                this.afterPrototypeCreation(beanName);
                            }
                        });
                        beanInstance = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        ...
        }
        ...
}


注意:

  • 单例和原型scope的Bean是硬编码单独处理的
  • 除了单例和原型Bean,其他Scope是由Scope对象处理的
  • 具体创建Bean的过程都是由IOC做的,只不过Bean的获取是通过Scope对象

根据不同的scope类型,spring 提供了下列多种scope实现:

 这次我们重点投了RefreshScope的实现。

RefreshScope的实现

RefreshScope extends GenericScope,其父类GenericScope的get方法实现获取Bean,注意创建Bean还是由IOC#createBean实现。

GenericScope#get

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
      //通过cache把bean缓存下来,如果不存在则创建
        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;
        }
    }

BeanLifecycleWrapperCache#put

    //这里将Bean包装并缓存下来,这里的cache为scopecache
    public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {
            return (BeanLifecycleWrapper) this.cache.put(name, value);
        }
StandardScopeCache
public class StandardScopeCache implements ScopeCache {
 
    private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
 
    public Object remove(String name) {
        return this.cache.remove(name);
    }
 
    public Collection<Object> clear() {
        Collection<Object> values = new ArrayList<Object>(this.cache.values());
        this.cache.clear();
        return values;
    }
 
    public Object get(String name) {
        return this.cache.get(name);
    }
 
    public Object put(String name, Object value) {
        // result若不等于null,表示缓存存在了,不会进行put操作
        Object result = this.cache.putIfAbsent(name, value);
        if (result != null) {
            // 直接返回旧对象
            return result;
        }
        // put成功,返回新对象
        return value;
    }
}

获取BeanLifecycleWrapper后,通过getBean方法获取实际Bean,如果Bean不存在,则调用scope#get传输进来的ObjectFactory创建Bean。

BeanLifecycleWrapperCache#getBean

public Object getBean() {
            if (this.bean == null) {
                synchronized (this.name) {
                    if (this.bean == null) {
                        this.bean = this.objectFactory.getObject();
                    }
                }
            }
            return this.bean;
        }


RefreshScope刷新

当配置中心刷新配置之后,有两种方式可以动态刷新Bean的配置变量值:

向上下文发布一个RefreshEvent事件

Http访问/refresh这个EndPoint

无论是哪个方式,最终都会调用

RefreshScope#refreshAll

    public void refreshAll() {
        super.destroy();
        this.context.publishEvent(new RefreshScopeRefreshedEvent());
    }

该方法会调用

GenericScope#destory

方法清空包装类缓存,然后发布一个RefreshScopeRefreshedEvent

  @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();
                }
            }
            catch (RuntimeException e) {
                errors.add(e);
            }
        }
        if (!errors.isEmpty()) {
            throw wrapIfNecessary(errors.get(0));
        }
        this.errors.clear();
    }

查看

RefreshScope#refreshAll 的引用,这个方法被ContextRefresher#refresh方法调用:

private RefreshScope scope;    
 
public synchronized Set<String> refresh() {
          // 刷新上下文环境变量
        Set<String> keys = refreshEnvironment();
          //调用scope对象的refreshAll方法
        this.scope.refreshAll();
        return keys;
    }
 
    public synchronized Set<String> refreshEnvironment() {
        Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
        updateEnvironment();
        Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
        this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
        return keys;
    }


查看ContextRefresher#refresh方法的引用,分别是:

RefreshEndpoint:Http访问/refresh这个EndPoint

RefreshEventListener:监听RefreshEvent事件

所以我们可以通过发起http://localhost:8080/actuator/refresh主动刷新,或者发送一个RefreshEvent事件。

这里以Nacos为例:

Nacos里定义了NacosContextRefresher,该类中注册了Nacos监听器,当监听到配置变更,则发送RefreshEvent事件。

  private void registerNacosListener(final String groupKey, final String dataKey) {
        String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
        Listener listener = (Listener)this.listenerMap.computeIfAbsent(key, (lst) -> {
            return new AbstractSharedListener() {
                public void innerReceive(String dataId, String group, String configInfo) {
                    NacosContextRefresher.refreshCountIncrement();
                    NacosContextRefresher.this.nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
                  //发送RefreshEvent事件
                    NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
                    if (NacosContextRefresher.log.isDebugEnabled()) {
                        NacosContextRefresher.log.debug(String.format("Refresh Nacos config group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo));
                    }
 
                }
            };
        });
 
        try {
            this.configService.addListener(dataKey, groupKey, listener);
        } catch (NacosException var6) {
            log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), var6);
        }
 
    }


经过上述的刷新动作后,cache缓存将被清空,那又是怎么获取最新值呢?

我们看回GenericScope#get就清楚了:

@Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
      //通过cache把bean缓存下来,如果不存在则创建
      //由于cache已被清空,所以获取的对象肯定是新聪IOC容器创建出来的,由于前面已经刷新了环境变量,所以新对象将包含最新配置值
        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;
        }
    }


参考资料

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值