Spring Cloud @RefreshScope 原理及使用
@RefreshScope那些事
要說(shuō)清楚RefreshScope,先要了解Scope
- Scope(org.springframework.beans.factory.config.Scope)是Spring 2.0開(kāi)始就有的核心的概念
- RefreshScope(org.springframework.cloud.context.scope.refresh)是spring cloud提供的一種特殊的scope實(shí)現(xiàn),用來(lái)實(shí)現(xiàn)配置、實(shí)例熱加載。
- Scope -> GenericScope -> RefreshScope

Scope與ApplicationContext生命周期
AbstractBeanFactory#doGetBean創(chuàng)建Bean實(shí)例
protected <T> T doGetBean(...){
final RootBeanDefinition mbd = ...
if (mbd.isSingleton()) {
...
} else if (mbd.isPrototype())
...
} else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {...});
...
}
...
}
Singleton和Prototype是硬編碼的,并不是Scope子類。 Scope實(shí)際上是自定義擴(kuò)展的接口
Scope Bean實(shí)例交由Scope自己創(chuàng)建,例如SessionScope是從Session中獲取實(shí)例的,ThreadScope是從ThreadLocal中獲取的,而RefreshScope是在內(nèi)建緩存中獲取的。
@Scope 對(duì)象的實(shí)例化
@RefreshScope 是scopeName="refresh"的 @Scope
...
@Scope("refresh")
public @interface RefreshScope {
...
}
@Scope 的注冊(cè) AnnotatedBeanDefinitionReader#registerBean
public void registerBean(...){
...
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
...
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
}
讀取@Scope元數(shù)據(jù), AnnotationScopeMetadataResolver#resolveScopeMetadata
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), Scope.class);
if (attributes != null) {
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;
}
metadata.setScopedProxyMode(proxyMode);
}
}
Scope實(shí)例對(duì)象通過(guò)ScopedProxyFactoryBean創(chuàng)建,其中通過(guò)AOP使其實(shí)現(xiàn)ScopedObject接口,這里不再展開(kāi)
現(xiàn)在來(lái)說(shuō)說(shuō)RefreshScope是如何實(shí)現(xiàn)配置和實(shí)例刷新的
RefreshScope注冊(cè)
RefreshAutoConfiguration#RefreshScopeConfiguration
@Component
@ConditionalOnMissingBean(RefreshScope.class)
protected static class RefreshScopeConfiguration implements BeanDefinitionRegistryPostProcessor{
...
registry.registerBeanDefinition("refreshScope",
BeanDefinitionBuilder.genericBeanDefinition(RefreshScope.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.getBeanDefinition());
...
}
RefreshScope extends GenericScope, 大部分邏輯在 GenericScope 中
GenericScope#postProcessBeanFactory 中向AbstractBeanFactory注冊(cè)自己
public class GenericScope implements Scope, BeanFactoryPostProcessor...{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
beanFactory.registerScope(this.name/*refresh*/, this/*RefreshScope*/);
...
}
}
RefreshScope 刷新過(guò)程
入口在ContextRefresher#refresh
refresh() {
Map<String, Object> before = ①extract(
this.context.getEnvironment().getPropertySources());
②addConfigFilesToEnvironment();
Set<String> keys = ④changes(before,
③extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.⑤publishEvent(new EnvironmentChangeEvent(keys));
this.scope.⑥r(nóng)efreshAll();
}
①提取標(biāo)準(zhǔn)參數(shù)(SYSTEM,JNDI,SERVLET)之外所有參數(shù)變量
②把原來(lái)的Environment里的參數(shù)放到一個(gè)新建的Spring Context容器下重新加載,完事之后關(guān)閉新容器
③提起更新過(guò)的參數(shù)(排除標(biāo)準(zhǔn)參數(shù))
④比較出變更項(xiàng)
⑤發(fā)布環(huán)境變更事件,接收:EnvironmentChangeListener/LoggingRebinder
⑥RefreshScope用新的環(huán)境參數(shù)重新生成Bean
重新生成的過(guò)程很簡(jiǎn)單,清除refreshscope緩存幷銷毀Bean,下次就會(huì)重新從BeanFactory獲取一個(gè)新的實(shí)例(該實(shí)例使用新的配置)
RefreshScope#refreshAll
public void refreshAll() {
<b>super.destroy();</b>
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
GenericScope#destroy
public void destroy() {
...
Collection<BeanLifecycleWrapper> wrappers = <b>this.cache.clear()</b>;
for (BeanLifecycleWrapper wrapper : wrappers) {
<b>wrapper.destroy();</b>
}
}
Spring Cloud Bus 如何觸發(fā) Refresh
BusAutoConfiguration#BusRefreshConfiguration 發(fā)布一個(gè)RefreshBusEndpoint
@Configuration
@ConditionalOnClass({ Endpoint.class, RefreshScope.class })
protected static class BusRefreshConfiguration {
@Configuration
@ConditionalOnBean(ContextRefresher.class)
@ConditionalOnProperty(value = "endpoints.spring.cloud.bus.refresh.enabled", matchIfMissing = true)
protected static class BusRefreshEndpointConfiguration {
@Bean
public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context,
BusProperties bus) {
return new RefreshBusEndpoint(context, bus.getId());
}
}
}
RefreshBusEndpoint 會(huì)從http端口觸發(fā)廣播RefreshRemoteApplicationEvent事件
@Endpoint(id = "bus-refresh")
public class RefreshBusEndpoint extends AbstractBusEndpoint {
public void busRefresh() {
publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), null));
}
}
BusAutoConfiguration#refreshListener 負(fù)責(zé)接收事件(所有配置bus的節(jié)點(diǎn))
@Bean
@ConditionalOnProperty(value = "spring.cloud.bus.refresh.enabled", matchIfMissing = true)
@ConditionalOnBean(ContextRefresher.class)
public RefreshListener refreshListener(ContextRefresher contextRefresher) {
return new RefreshListener(contextRefresher);
}
RefreshListener#onApplicationEvent 觸發(fā) ContextRefresher
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
Set<String> keys = contextRefresher.refresh();
}
大部分需要更新的服務(wù)需要打上@RefreshScope, EurekaClient是如何配置更新的
EurekaClientAutoConfiguration#RefreshableEurekaClientConfiguration
@Configuration
@ConditionalOnRefreshScope
protected static class RefreshableEurekaClientConfiguration{
@Bean
@RefreshScope
public EurekaClient eurekaClient(...) {
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}
@Bean
@RefreshScope
public ApplicationInfoManager eurekaApplicationInfoManager(...) {
...
return new ApplicationInfoManager(config, instanceInfo);
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過(guò)程
這篇文章主要介紹了Java靜態(tài)內(nèi)部類實(shí)現(xiàn)單例過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
Spring Cloud Gateway全局通用異常處理的實(shí)現(xiàn)
這篇文章主要介紹了Spring Cloud Gateway全局通用異常處理的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Docker環(huán)境下Spring Boot應(yīng)用內(nèi)存飆升分析與解決場(chǎng)景分析
當(dāng)運(yùn)行一個(gè)Spring Boot項(xiàng)目時(shí),如果未設(shè)置JVM內(nèi)存參數(shù),Spring Boot默認(rèn)會(huì)采用JVM自身默認(rèn)的配置策略,接下來(lái)通過(guò)本文給大家介紹Docker環(huán)境下Spring Boot應(yīng)用內(nèi)存飆升分析與解決方法,需要的朋友參考下吧2021-08-08
關(guān)于JAVA中stream流的基礎(chǔ)處理(獲取對(duì)象字段和對(duì)象批量處理等)
這篇文章主要介紹了關(guān)于JAVA中stream流的基礎(chǔ)處理,包含獲取對(duì)象字段、按字段排序、按字段去重、對(duì)象批量處理、指定字段轉(zhuǎn)數(shù)組等內(nèi)容,需要的朋友可以參考下2023-03-03
MybatisPlus使用@TableId主鍵id自增長(zhǎng)無(wú)效的解決
本文主要介紹了MybatisPlus使用@TableId主鍵id自增長(zhǎng)無(wú)效的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
在jmeter的beanshell中用java獲取系統(tǒng)當(dāng)前時(shí)間的簡(jiǎn)單實(shí)例
這篇文章介紹了在jmeter的beanshell中用java獲取系統(tǒng)當(dāng)前時(shí)間的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-09-09
SpringBoot實(shí)現(xiàn)識(shí)別圖片中的身份證號(hào)與營(yíng)業(yè)執(zhí)照信息
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)識(shí)別圖片中的身份證號(hào)與營(yíng)業(yè)執(zhí)照信息,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-01-01

