一文带你理解@RefreshScope注解实现动态刷新原理

 更新时间:2023年07月16日 14:33:08   作者:JAVA旭阳  
RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新Bean中的属性配置,那大家对他的实现原理了解吗,它为什么可以做到动态刷新呢,所以本文小编将给大家详细介绍@RefreshScope注解实现动态刷新原理

注解的作用

@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;
}
  • 上面是RefreshScope的源码,该注解被@Scope注解使用,@Scope用来比较Spring Bean的作用域,具体使用参考相关文章。
  • 注解的属性proxyMode默认使用TARGET_CLASS作为代理。

实例

  • controller中添加@RefreshScope

  • nacos配置中心中配置

  • 验证, 修改配置中心后,可以不重启动,刷新配置

  • 去掉@RefreshScope 就不会自动刷新。

 代码地址:awesome-springcloud-demo/springcloud-nacos/springcloud-nacos-config at master · alvinlkk/awesome-springcloud-demo · GitHub

原理解析

为了实现动态刷新配置,主要就是想办法达成以下两个核心目标:

  • 让Spring容器重新加载Environment环境配置变量
  • Spring Bean重新创建生成

@RefreshScope主要就是基于@Scope注解的作用域代理的基础上进行扩展实现的,加了@RefreshScope注解的类,在被Bean工厂创建后会加入自己的refresh scope 这个Bean缓存中,后续会优先从Bean缓存中获取,当配置中心发生了变更,会把变更的配置更新到spring容器的Environment中,并且同事bean缓存就会被清空,从而就会从bean工厂中创建bean实例了,而这次创建bean实例的时候就会继续经历这个bean的生命周期,使得@Value属性值能够从Environment中获取到最新的属性值,这样整个过程就达到了动态刷新配置的效果。

获取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);
	}
}
  • 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去深入理解。

以上就是一文带你理解@RefreshScope注解实现动态刷新原理的详细内容,更多关于@RefreshScope注解实现动态刷新的资料请关注脚本之家其它相关文章!

相关文章

  • Android实现兼容的水波纹效果

    Android实现兼容的水波纹效果

    Android的水波纹效果只有高版本才有,我们希望自己的应用在低版本用低版本的阴影,而高版本用水波纹,这怎么做呢?下面一起来学习学习。
    2016-08-08
  • RocketMQ延迟消息简明介绍

    RocketMQ延迟消息简明介绍

    这篇文章主要介绍了RocketMQ延迟消息,延迟消息是个啥?顾名思义,就是等一段时间再消费的消息。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 简单了解SpringBoot HATEOAS使用方法

    简单了解SpringBoot HATEOAS使用方法

    这篇文章主要介绍了简单了解SpringBoot HATEOAS使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • java ssm框架的controller实现向页面传递参数

    java ssm框架的controller实现向页面传递参数

    这篇文章主要介绍了java ssm框架的controller实现向页面传递参数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • Linux下Hbase安装配置教程

    Linux下Hbase安装配置教程

    本文给大家介绍了Linux下Hbase安装配置教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-11-11
  • Java内存模型之重排序的相关知识总结

    Java内存模型之重排序的相关知识总结

    重排序是指编译器和处理器为了优化性能而对指令序列进行重新排序的一种手段,文中详细介绍了Java重排序的相关知识,需要的朋友可以参考下
    2021-06-06
  • Java EasyExcel实现合并相同内容单元格与动态标题功能

    Java EasyExcel实现合并相同内容单元格与动态标题功能

    这篇文章主要为大家详细介绍了Java EasyExcel如何实现合并相同内容单元格与动态标题功能,文中的示例代码讲解详细,有需要的小伙伴可以参考下
    2023-12-12
  • logback自定义json日志输出示例详解

    logback自定义json日志输出示例详解

    这篇文章主要为大家介绍了logback自定义json日志输出,就是通过logback日志体系以及logstash提供的json log依赖将数据以json格式记录到日志文件的例子
    2022-03-03
  • SpringBoot+Redis海量重复提交问题解决

    SpringBoot+Redis海量重复提交问题解决

    在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,所以本文介绍一下SpringBoot+Redis海量重复提交问题解决,感兴趣的可以了解一下
    2023-12-12
  • 关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项

    关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项

    这篇文章主要介绍了关于scanner.nextInt()等next()和scanner.nextIine()连用注意事项,具有很好的参考价值,希望对大家有所帮助。
    2023-04-04

最新评论