已知条件
加上@RefreshScope注解的类的bean,在配置文件更新的时会刷新bean。
思考如何做到动态刷新bean?
猜想:
-
既然是bean,那么这个被@RefreshScope修饰的bean肯定也在ioc容器中,那它容器中的作用域肯定不是单例,因为单例只会实例化一次,不会重复创建,猜测这个bean要么是原型,要么是其他作用域
-
在nacos源码中是否存在监听器,当监听到配置文件发生后处理刷新处理这次变化(具体如何处理暂时不知道,但这次变化必然会通知ioc容器去重新创建需要刷新的bean,只是有这个猜想)
-
ioc如何刷新变化的bean
验证:
验证@RefreshScope修饰的bean作用域
-
查看@RefreshScope代码
-
-
这个类,我们看见了两个关键信息:
-
@Scope("refresh") :Spring中Scope 作用域名字为 refresh
-
ScopedProxyMode.TARGET_CLASS: 通过CGLIB代码实现
-
-
重点关注@Scope("refresh")
-
而在spring中Scope作用域有默认是单例模式,即scope="singleton",另外scope还有prototype、request、session、global session作用域,但无refresh作用域,看来这个才是重点。
-
查看spring容器
-
1、找到AbstractBeanFactory这个类,这个类是Spring ioc容器核心类,bean工厂类,主要用于创建bean和获取bean的
-
2、找到doGetBean方法
-
-
3、找到GenericScope实现类
-
-
这里我们看见了缓存它使用了一个put方法,然后返回了一个BeanLifecycleWrapper 对象,这个对象调用getBean方法返回了一个bean,我们点进去看这个put方法实现,发现使用了putIfAbsent方法
-
-
这个方法,顾名思义就是put之前先判断是否存在,若存在则直接返回,不存在就放入BeanLifecycleWrapper这个对象其实就是上面的代码
-
-
那我们就定位到了createBean方法了,点进去看其实就是spring创建bean,调用 doCreateBean,然后里面就是实例化bean,初始化bean,最后返回bean
-
-
这里得出两个结论:
- Nacos修改了配置文件后,在Spring ioc容器每次会从缓存中去取BeanLifecycleWrapper对象,如果存在就直接通过这个对象的getBean方法直接返回bean,若不存在则需要重新执行return createBean(beanName, mbd, args); 方法。
- Nacos修改了配置文件后肯定将缓存里面的对象清空了,不然它也不可能重新去创建对象,这个就涉及到下面的内容了。
验证nacos源码中是存在监听器,并且会通知ioc容器
-
我们通过启动日志会发现一些特殊的日志打印
-
-
关键类我们点进去看看
-
CacheData:我们看日志打印了,config listener notify warn timeout millis use default 60000 millis,说明监听器肯定跟这个类有关系。
-
进去查看一下:
-
-
果然有监听器,发现真正使用监听器的方式是checkListenerMd5()
-
跟进这个方法:
-
-
一直找下去,找到真正实现这个方法的类中
-
-
最终我们找到了NacosContextRefresher这个类。
-
然后搜索上面抽象类的重写的innerReceive方法。
-
-
- 这里看出了它发送了一个RefreshEvent 事件到Spring 容器中。
验证ioc如何刷新变化的bean
- 前提:前面我们知道了,nacos在修改配置文件之后向Spring容器发送了一个RefreshEvent 事件。
- 那我们去Spring中找到这个RefreshEvent事件处理的地方就OK了对吧。
- 既然是RefreshEvent,那肯定有对应的类去处理的。
- 那我们直接在IDEA中搜索,Refresh,看能找出一些什么东西。
- 这两个类看着有点像,我们点进去看看:
- RefreshAutoConfiguration
- 这个类,创建了一个RefreshEventListener,我们再去看RefreshEventListener
- RefreshEventListener
- 定位到了handle(RefreshEvent event) 方法了,进一步跟进去看看。
- 销毁方法跟进去看看:
- 将里面的对象也全部销毁了。
- destroy方法会将当前存储的refreshScope类型的真实的对象清空,然后调用一些销毁的回调方法。 前面我们提到了refreshScope真实对象生成后会放入这个cache中以便下次使用,如果缓存中没有则会生成一个新的。 新生成的对象就会通过ioc的populate、initialize方法重新依赖注入新的属性了,这样我们就是实现了配置的热更新,对于使用者而言没有感觉,我们依旧使用的是我们的代理对象,但是其背部的真实对象可能重新生成了。
总结:
- @RefreshScope主要就是基于@Scope注解的作用域代理的基础上进行扩展实现的,加了@RefreshScope注解的类。
- 在被Bean工厂创建后会加入自己的refresh scope 这个Bean缓存中,后续会优先从Bean缓存中获取,当配置中心发生了变更,会把变更的配置更新到spring容器的Environment中,并且同时bean缓存就会被清空,从而就会从bean工厂中创建bean实例了,而这次创建bean实例的时候就会继续经历这个bean的生命周期,使得@Value属性值能够从Environment中获取到最新的属性值,这样整个过程就达到了动态刷新配置的效果。