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


    概述

    RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,他可以用来刷新Bean中的属性配置,那大家对他的实现原理了解吗?它为什么可以做到动态刷新呢?

    注解的作用

    @RefreshScope注解是Spring Cloud中的一个注解,用来实现Bean中属性的动态刷新。

    1. /**
    2. * Convenience annotation to put a @Bean definition in
    3. * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}.
    4. * Beans annotated this way can be refreshed at runtime and any components that are using
    5. * them will get a new instance on the next method call, fully initialized and injected
    6. * with all dependencies.
    7. *
    8. * @author Dave Syer
    9. *
    10. */
    11. @Target({ ElementType.TYPE, ElementType.METHOD })
    12. @Retention(RetentionPolicy.RUNTIME)
    13. @Scope("refresh")
    14. @Documented
    15. public @interface RefreshScope {
    16. /**
    17. * @see Scope#proxyMode()
    18. * @return proxy mode
    19. */
    20. ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
    21. }
    22. 复制代码
    • 上面是RefreshScope的源码,该注解被@Scope注解使用,@Scope用来比较Spring Bean的作用域,具体使用参考相关文章。
    • 注解的属性proxyMode默认使用TARGET_CLASS作为代理。

    实例

    1. controller中添加@RefreshScope

    1. nacos配置中心中配置

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

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

    代码地址: github.com/alvinlkk/aw…

    原理解析

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

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

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

    如果对Scope注解不清楚的可以阅读这篇文章:【Spring注解必知必会】@Scope注解源码解析

    获取RefreshScope注解的Bean

    通过打上断点查看堆栈可知:

    1. 因为Class被加上了@RefreshScope注解,那么这个BeanDefinition信息中的scope为refresh,在getBean的的时候会单独处理逻辑。
    1. public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    2. protected <T> T doGetBean(
    3. String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    4. throws BeansException {
    5. // 如果scope是单例的情况, 这里不进行分析
    6. if (mbd.isSingleton()) {
    7. .....
    8. }
    9. // 如果scope是prototype的情况, 这里不进行分析
    10. else if (mbd.isPrototype()) {
    11. ......
    12. }
    13. // 如果scope是其他的情况,本例中是reresh
    14. else {
    15. String scopeName = mbd.getScope();
    16. if (!StringUtils.hasLength(scopeName)) {
    17. throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
    18. }
    19. // 获取refresh scope的实现类RefreshScope,这个类在哪里注入,我们后面讲
    20. Scope scope = this.scopes.get(scopeName);
    21. if (scope == null) {
    22. throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
    23. }
    24. try {
    25. // 这边是获取bean,调用的是RefreshScope中的的方法
    26. Object scopedInstance = scope.get(beanName, () -> {
    27. beforePrototypeCreation(beanName);
    28. try {
    29. return createBean(beanName, mbd, args);
    30. }
    31. finally {
    32. afterPrototypeCreation(beanName);
    33. }
    34. });
    35. beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
    36. }
    37. catch (IllegalStateException ex) {
    38. throw new ScopeNotActiveException(beanName, scopeName, ex);
    39. }
    40. }
    41. }
    42. catch (BeansException ex) {
    43. beanCreation.tag("exception", ex.getClass().toString());
    44. beanCreation.tag("message", String.valueOf(ex.getMessage()));
    45. cleanupAfterBeanCreationFailure(beanName);
    46. throw ex;
    47. }
    48. finally {
    49. beanCreation.end();
    50. }
    51. }
    52. return adaptBeanInstance(name, beanInstance, requiredType);
    53. }
    54. }
    55. 复制代码

    2.RefreshScope继承成了GenericScope类,最终调用的的是GenericScope的get方法

    1. public class GenericScope
    2. implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
    3. @Override
    4. public Object get(String name, ObjectFactory objectFactory) {
    5. // 将bean添加到缓存cache中
    6. BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
    7. this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
    8. try {
    9. // 调用下面的getBean方法
    10. return value.getBean();
    11. }
    12. catch (RuntimeException e) {
    13. this.errors.put(name, e);
    14. throw e;
    15. }
    16. }
    17. private static class BeanLifecycleWrapper {
    18. public Object getBean() {
    19. // 如果bean为空,则创建bean
    20. if (this.bean == null) {
    21. synchronized (this.name) {
    22. if (this.bean == null) {
    23. this.bean = this.objectFactory.getObject();
    24. }
    25. }
    26. }
    27. // 否则返回之前创建好的bean
    28. return this.bean;
    29. }
    30. }
    31. }
    32. 复制代码

    小结:

    从这边的代码中可以印证了上面的说法,创建后的Bean会缓存到scope的cache中,优先从缓存中获取,如果缓存中是null, 则重新走一遍create bean的流程。

    RefeshScope Bean的创建

    上面的在getBean的时候依赖到RefreshScope这个Bean,那么这个Bean是在什么时候加入到Spring Bean中的呢?答案就是RefreshAutoConfiguration

    配置中心刷新后刷新Bean缓存

    1. 配置中心发生变化后,会收到一个RefreshEvent事件,RefreshEventListner监听器会监听到这个事件。
    1. public class RefreshEventListener implements SmartApplicationListener {
    2. ........
    3. public void handle(RefreshEvent event) {
    4. if (this.ready.get()) { // don't handle events before app is ready
    5. log.debug("Event received " + event.getEventDesc());
    6. // 会调用refresh方法,进行刷新
    7. Set<String> keys = this.refresh.refresh();
    8. log.info("Refresh keys changed: " + keys);
    9. }
    10. }
    11. }
    12. // 这个是ContextRefresher类中的刷新方法
    13. public synchronized Set<String> refresh() {
    14. // 刷新spring的envirionment 变量配置
    15. Set<String> keys = refreshEnvironment();
    16. // 刷新其他scope
    17. this.scope.refreshAll();
    18. return keys;
    19. }
    20. 复制代码
    1. refresh方法最终调用destroy方法,清空之前缓存的bean
    1. public class RefreshScope extends GenericScope
    2. implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
    3. @ManagedOperation(description = "Dispose of the current instance of all beans "
    4. + "in this scope and force a refresh on next method execution.")
    5. public void refreshAll() {
    6. // 调用父类的destroy
    7. super.destroy();
    8. this.context.publishEvent(new RefreshScopeRefreshedEvent());
    9. }
    10. }
    11. @Override
    12. public void destroy() {
    13. List errors = new ArrayList();
    14. Collection wrappers = this.cache.clear();
    15. for (BeanLifecycleWrapper wrapper : wrappers) {
    16. try {
    17. Lock lock = this.locks.get(wrapper.getName()).writeLock();
    18. lock.lock();
    19. try {
    20. // 这里主要就是把之前的bean设置为null, 就会重新走createBean的流程了
    21. wrapper.destroy();
    22. }
    23. finally {
    24. lock.unlock();
    25. }
    26. }
    27. catch (RuntimeException e) {
    28. errors.add(e);
    29. }
    30. }
    31. if (!errors.isEmpty()) {
    32. throw wrapIfNecessary(errors.get(0));
    33. }
    34. this.errors.clear();
    35. }
    36. 复制代码

    总结

    上面是这个RefreshScope实现动态刷新大致的原理,其中里面还有很多细节,可能需要留给大家自己debug去深入理解。

     

  • 相关阅读:
    解决动态菜单router的index配置,以及第二次传参未响应情况
    【仿牛客网笔记】 Spring Boot进阶,开发社区核心功能-统一处理异常
    人生旅途之解锁悉尼
    健身用什么耳机,几款比较适合健身场景的耳机推荐
    js函数变量提升理解
    脚本自动更新、关闭、启动服务器【BAT、SHELL、SVN,NPM】
    数学建模学习(95):MO-JAYA算法对多目标(多元)函数寻优求解
    实践分享:vue模块化基本用法
    java8 Stream流常用方法(持续更新中...)
    团建游戏------飞人降落
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/126319418