Spring Boot:@RefreshScope原理
@RefreshScope的作用经过@RefreshScope注解修饰的bean,将被RefreshScope进行代理,用来实现配置、实例热加载,即当配置变更时可以在不重启应用的前提下刷新bean中相关的属性值。@RefreshScope注解@RefreshScope的实现如下,非常简单,最主要是@Scope("refresh")和ScopedProxyMode.TARGET_CLASS,表示@R
@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;
}
}
参考资料
更多推荐
所有评论(0)