@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;
		}
    }

参考资料

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐