概述
RefeshScope 这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新 Bean 中的属性配置,那大家对他的实现原理了解吗?它为什么可以做到动态刷新呢?
注解的作用
@RefreshScope
注解是 Spring Cloud 中的一个注解,用来实现 Bean 中属性的动态刷新。
/**
* Convenience annotation to put a <code>@Bean</code> definition in
* {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
* Beans annotated this way can be refreshed at runtime and any components that are using
* them will get a new instance on the next method call, fully initialized and injected
* with all dependencies.
*
* @author Dave Syer
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
* @return proxy mode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
复制代码
实例
controller 中添加@RefreshScope
nacos 配置中心中配置
验证, 修改配置中心后,可以不重启动,刷新配置
去掉@RefreshScope
就不会自动刷新。
代码地址: https://github.com/alvinlkk/awesome-springcloud-demo/tree/master/springcloud-nacos/springcloud-nacos-config
原理解析
为了实现动态刷新配置,主要就是想办法达成以下两个核心目标:
让 Spring 容器重新加载 Environment 环境配置变量
Spring Bean 重新创建生成
@RefreshScope
主要就是基于@Scope
注解的作用域代理的基础上进行扩展实现的,加了@RefreshScope
注解的类,在被 Bean 工厂创建后会加入自己的 refresh scope 这个 Bean 缓存中,后续会优先从 Bean 缓存中获取,当配置中心发生了变更,会把变更的配置更新到 spring 容器的 Environment 中,并且同事 bean 缓存就会被清空,从而就会从 bean 工厂中创建 bean 实例了,而这次创建 bean 实例的时候就会继续经历这个 bean 的生命周期,使得 @Value 属性值能够从 Environment 中获取到最新的属性值,这样整个过程就达到了动态刷新配置的效果。
如果对 Scope 注解不清楚的可以阅读这篇文章:【Spring注解必知必会】@Scope注解源码解析
获取 RefreshScope 注解的 Bean
通过打上断点查看堆栈可知:
因为 Class 被加上了@RefreshScope
注解,那么这个 BeanDefinition 信息中的 scope 为 refresh,在 getBean 的的时候会单独处理逻辑。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 如果scope是单例的情况, 这里不进行分析
if (mbd.isSingleton()) {
.....
}
// 如果scope是prototype的情况, 这里不进行分析
else if (mbd.isPrototype()) {
......
}
// 如果scope是其他的情况,本例中是reresh
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
// 获取refresh scope的实现类RefreshScope,这个类在哪里注入,我们后面讲
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
// 这边是获取bean,调用的是RefreshScope中的的方法
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new ScopeNotActiveException(beanName, scopeName, ex);
}
}
}
catch (BeansException ex) {
beanCreation.tag("exception", ex.getClass().toString());
beanCreation.tag("message", String.valueOf(ex.getMessage()));
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
finally {
beanCreation.end();
}
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
}
复制代码
2.RefreshScope 继承成了 GenericScope 类,最终调用的的是 GenericScope 的 get 方法
public class GenericScope
implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 将bean添加到缓存cache中
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
// 调用下面的getBean方法
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
}
private static class BeanLifecycleWrapper {
public Object getBean() {
// 如果bean为空,则创建bean
if (this.bean == null) {
synchronized (this.name) {
if (this.bean == null) {
this.bean = this.objectFactory.getObject();
}
}
}
// 否则返回之前创建好的bean
return this.bean;
}
}
}
复制代码
小结:
从这边的代码中可以印证了上面的说法,创建后的 Bean 会缓存到 scope 的 cache 中,优先从缓存中获取,如果缓存中是 null, 则重新走一遍 create bean 的流程。
RefeshScope Bean 的创建
上面的在 getBean 的时候依赖到 RefreshScope 这个 Bean,那么这个 Bean 是在什么时候加入到 Spring Bean 中的呢?答案就是RefreshAutoConfiguration
。
配置中心刷新后刷新 Bean 缓存
配置中心发生变化后,会收到一个RefreshEvent
事件,RefreshEventListner
监听器会监听到这个事件。
public class RefreshEventListener implements SmartApplicationListener {
........
public void handle(RefreshEvent event) {
if (this.ready.get()) { // don't handle events before app is ready
log.debug("Event received " + event.getEventDesc());
// 会调用refresh方法,进行刷新
Set<String> keys = this.refresh.refresh();
log.info("Refresh keys changed: " + keys);
}
}
}
// 这个是ContextRefresher类中的刷新方法
public synchronized Set<String> refresh() {
// 刷新spring的envirionment 变量配置
Set<String> keys = refreshEnvironment();
// 刷新其他scope
this.scope.refreshAll();
return keys;
}
复制代码
refresh 方法最终调用 destroy 方法,清空之前缓存的 bean
public class RefreshScope extends GenericScope
implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
@ManagedOperation(description = "Dispose of the current instance of all beans "
+ "in this scope and force a refresh on next method execution.")
public void refreshAll() {
// 调用父类的destroy
super.destroy();
this.context.publishEvent(new 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 {
// 这里主要就是把之前的bean设置为null, 就会重新走createBean的流程了
wrapper.destroy();
}
finally {
lock.unlock();
}
}
catch (RuntimeException e) {
errors.add(e);
}
}
if (!errors.isEmpty()) {
throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
复制代码
总结
上面是这个 RefreshScope 实现动态刷新大致的原理,其中里面还有很多细节,可能需要留给大家自己 debug 去深入理解。
参考
https://blog.csdn.net/qq_29310729/article/details/115307530
https://blog.csdn.net/weixin_37689658/article/details/122424294?ops_request_misc=&request_id=&biz_id=102&utm_term=refreshscope 代理&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-122424294.142^v40^pc_rank_34_2,185^v2^control&spm=1018.2226.3001.4187
评论