今天我们来看一下Springcloud的服务配置与刷新是怎么工作的
通过这个理解我们可以做相应的扩展,例如如何像nacos/apollo那样编写自己工作需要的服务配置与刷新代码
先介绍一下核心的接口
PropertySource表示配置源
PropertySourceLocator表示配置源定位器(加载器)
启动配置加载
我们先来看Springboot启动时的配置加载过程,来看PropertySourceBootstrapConfiguration
它实现了ApplicationContextInitializer
在Springboot启动过程 Springboot 启动过程_icodegarden的博客-CSDN博客 中我们知道所有的ApplicationContextInitializer会在启动阶段的prepareContext中触发initialize
下面我们来看PropertySourceBootstrapConfiguration的initialize
这里的主要过程是
1、通过locator.locateCollection(environment)获取PropertySource,这里的locator就是PropertySourceLocator
2、把获取到的PropertySource加入到Environment
这样Springboot就准备好了Environment,其中包含配置中心的配置
现在我们来看PropertySourceLocator
以nacos为例看它的实现NacosPropertySourceLocator
看主要的loadApplicationConfiguration加载服务对应的配置
以上配置都是通过loadNacosDataIfPresent方法加载
最后通过configService.getConfig获取数据,内部的http请求不再详细展开
以上是Springboot启动阶段的配置加载过程
动态刷新
我们再来看配置动态刷新的过程,在触发方面没有统一的规范,不同的厂商有不同的方式,我们还是看nacos的实现,有个NacosContextRefresher
创建Listener
在Springboot启动过程 Springboot 启动过程_icodegarden的博客-CSDN博客 中我们知道ApplicationReadyEvent是在启动的最后阶段发出的,下面是收到Event后的处理
我们可以看到这里创建了一个Listener,并且configService.addListener,这里创建的Listener将在后面触发回调,暂且不提,我们看configService有什么内容
来到NacosContextRefresher的构造方法
configService是在这里被创建的
看NacosConfigService的构造方法
构造方法中又创建了ClientWorker
ClientWorker中开启了checkConfigInfo的调度
长轮询配置变更
重点来了,这里创建了LongPollingRunnable的任务,下面展示run方法中的关键部分
这段是检查本地配置是否有变化,有变化则在cacheData.checkListenerMd5();中触发之前创建的Listener
这段做3个事情
1、使用http long poll(长轮询,这里默认的Long-Pulling-Timeout是30000ms,read timeout是45000ms)检查nacos server端的配置是否有变化,返回有变化的keys
2、有变化的则获取新的配置
3、在cacheData.checkListenerMd5();中触发之前创建的Listener
最后这段是让LongPollingRunnable保持循环执行
通知Listener
我们继续看cacheData.checkListenerMd5();
本地维护的缓存MD5值由变化则表示配置有变,然后执行listener.receiveConfigInfo(contentTmp);
现在我们要回到之前所创建的Listener了
这里发出了RefreshEvent
刷新配置
继续来看RefreshEvent的监听器RefreshEventListener
这里的refresh在ContextRefresher,所做的工作是配置刷新,RefreshEventListener的完整类名是org.springframework.cloud.endpoint.event.RefreshEventListener,是Spring所定义的,之后的配置刷新过程的代码与配置中心厂商就无关了
我们继续进去看
refreshEnvironment即刷新context的Environment,updateEnvironment有2种实现
LegacyContextRefresher和ConfigDataContextRefresher
LegacyContextRefresher的过程是通过启动一次模拟的Application来加载完整的配置给新的Environment,加载同样也使用PropertySourceLocator(实现类例如NacosPropertySourceLocator),这里整个过程无需厂商再实现其他接口
ConfigDataContextRefresher的过程则需要厂商再实现相应的接口规范
最后使用this.scope.refreshAll();来达到刷新配置的目的
@Value的值就放在StandardScopeCache
刷新原理AOP
奇怪的是我们看到的是把cache清理了,而没有看到诸如把配置值的进行替换的代码,为什么我们获取到的@Value的值就是新的了呢
其实奥秘还是在Spring的AOP中,当我们的请求访问有@RefreshScope的bean的方法时,首先会经过AOP的chain,关于AOP我们可以回顾 Spring AOP是怎么工作的_icodegarden的博客-CSDN博客 ,而chain中的其中一个MethodInterceptor是LockedScopedProxyFactoryBean
这里将会先访问GenericScope的get(String name, ObjectFactory<?> objectFactory)
最后访问StandardScopeCache的以下方法
当配置更新后,就会触发上述的@Value缓存值清理,因此再来访问时就没有对应的值了,会把新的值设置到缓存中,于是最终我们获取到的@Value值是新的
以上就是Springboot配置的动态刷新过程