@RefreshScope注解Bean刷新初步分析

376 阅读4分钟

已知条件

加上@RefreshScope注解的类的bean,在配置文件更新的时会刷新bean。

思考如何做到动态刷新bean?

猜想:

  1. 既然是bean,那么这个被@RefreshScope修饰的bean肯定也在ioc容器中,那它容器中的作用域肯定不是单例,因为单例只会实例化一次,不会重复创建,猜测这个bean要么是原型,要么是其他作用域

  2. 在nacos源码中是否存在监听器,当监听到配置文件发生后处理刷新处理这次变化(具体如何处理暂时不知道,但这次变化必然会通知ioc容器去重新创建需要刷新的bean,只是有这个猜想)

  3. ioc如何刷新变化的bean

验证:

验证@RefreshScope修饰的bean作用域

  1. 查看@RefreshScope代码

    • image1.png

    • 这个类,我们看见了两个关键信息:

    • @Scope("refresh") :Spring中Scope 作用域名字为 refresh

    • ScopedProxyMode.TARGET_CLASS: 通过CGLIB代码实现

    • image2.png

    • 重点关注@Scope("refresh")

    • 而在spring中Scope作用域有默认是单例模式,即scope="singleton",另外scope还有prototype、request、session、global session作用域,但无refresh作用域,看来这个才是重点。

    • 查看spring容器

    • 1、找到AbstractBeanFactory这个类,这个类是Spring ioc容器核心类,bean工厂类,主要用于创建bean和获取bean的

    • 2、找到doGetBean方法

    • image3.png

    • 3、找到GenericScope实现类

    • image4.png

    • 这里我们看见了缓存它使用了一个put方法,然后返回了一个BeanLifecycleWrapper 对象,这个对象调用getBean方法返回了一个bean,我们点进去看这个put方法实现,发现使用了putIfAbsent方法

    • image5.png

    • 这个方法,顾名思义就是put之前先判断是否存在,若存在则直接返回,不存在就放入BeanLifecycleWrapper这个对象其实就是上面的代码

    • image6.png

    • 那我们就定位到了createBean方法了,点进去看其实就是spring创建bean,调用 doCreateBean,然后里面就是实例化bean,初始化bean,最后返回bean

    • image7.png

这里得出两个结论:

  1. Nacos修改了配置文件后,在Spring ioc容器每次会从缓存中去取BeanLifecycleWrapper对象,如果存在就直接通过这个对象的getBean方法直接返回bean,若不存在则需要重新执行return createBean(beanName, mbd, args); 方法。
  2. Nacos修改了配置文件后肯定将缓存里面的对象清空了,不然它也不可能重新去创建对象,这个就涉及到下面的内容了。

验证nacos源码中是存在监听器,并且会通知ioc容器

  1. 我们通过启动日志会发现一些特殊的日志打印

    • image8.png

    • 关键类我们点进去看看

    • CacheData:我们看日志打印了,config listener notify warn timeout millis use default 60000 millis,说明监听器肯定跟这个类有关系。

    • 进去查看一下:

    • image9.png

    • 果然有监听器,发现真正使用监听器的方式是checkListenerMd5()

    • 跟进这个方法:

    • image10.png

    • 一直找下去,找到真正实现这个方法的类中

    • image11.png

    • 最终我们找到了NacosContextRefresher这个类。

    • 然后搜索上面抽象类的重写的innerReceive方法。

    • image12.png

  • 这里看出了它发送了一个RefreshEvent 事件到Spring 容器中。

验证ioc如何刷新变化的bean

  • 前提:前面我们知道了,nacos在修改配置文件之后向Spring容器发送了一个RefreshEvent 事件。
  • 那我们去Spring中找到这个RefreshEvent事件处理的地方就OK了对吧。
  • 既然是RefreshEvent,那肯定有对应的类去处理的。
  • 那我们直接在IDEA中搜索,Refresh,看能找出一些什么东西。
  • image13.png
  • 这两个类看着有点像,我们点进去看看:
  • RefreshAutoConfiguration
  • 这个类,创建了一个RefreshEventListener,我们再去看RefreshEventListener
  • image14.png
  • RefreshEventListener
  • image15.png
  • 定位到了handle(RefreshEvent event) 方法了,进一步跟进去看看。
  • image16.png
  • image17.png
  • 销毁方法跟进去看看:
  • image18.png
  • 将里面的对象也全部销毁了。
  • image19.png
  • destroy方法会将当前存储的refreshScope类型的真实的对象清空,然后调用一些销毁的回调方法。 前面我们提到了refreshScope真实对象生成后会放入这个cache中以便下次使用,如果缓存中没有则会生成一个新的。 新生成的对象就会通过ioc的populate、initialize方法重新依赖注入新的属性了,这样我们就是实现了配置的热更新,对于使用者而言没有感觉,我们依旧使用的是我们的代理对象,但是其背部的真实对象可能重新生成了。

总结:

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