Spring学习之ContextRefresher
前言
工作中,遇到了可以监听通知不需要重启容器就可以自动更改Spring 中配置的方法,看了源码发现是利用了ContextRefresher。之前研究微服务的时候了解过全局配置,当时是主动触发/refresh接口实现的,其实原理都是一样,都是通过ContextRefresher,只是触发的方式不同罢了,这篇文章主要研究一下其源码和流程。
@RefreshScope
首先了解这个注解,只有这个注解标注的类才具有刷新功能。
spring 的是scope
Scope(org.springframework.beans.factory.config.Scope
)是Spring 2.0开始就有的核心的概念,我们熟悉的有singleton、property两种。他表示存储bean实例的范围,前者表示存储单例bean的区域,后者表示存储原型bean的区域。
RefreshScope(org.springframework.cloud.context.scope.refresh
)是spring cloud提供的一种特殊的scope实现,用来实现配置、实例热加载。也就是用来存储可以刷新bean的区域。
Scope -> GenericScope -> RefreshScope
-
Scope 与ApplicationContext生命周期
-
AbstractBeanFactory#doGetBean创建Bean实例
-
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>() {...}); ... } ... }
-
可以看到如果这个bean是除了单例和原型之外的scope, 它的获取是由Scope来获取的。例如SessionScope是从Session中获取实例的,ThreadScope是从ThreadLocal中获取的,而RefreshScope是在内建缓存中获取的。
-
-
@Scope 对象的实例化
-
@RefreshScope是scopeName = "refresh"的@Scope
-
@Scope 怎么被注册到容器中context
org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } abd.setInstanceSupplier(supplier); ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
-
解析@Scope元数据,org.springframework.context.annotation.AnnotationScopeMetadataResolver#resolveScopeMetadata;就是根据beanDefinition生成ScopeMetaData.
-
RefreshScope中的bean如何实现配置和实例刷新的
RefreshScope 注册
- RefreshAutoConfiguration#RefreshScopeConfiguration
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
RefreshScope scope) {
return new ContextRefresher(context, scope);
}
这两个bean都是以后要用的,第一个是RefreshScope,用来存储标记了@RefreshScope注解的容器。第二个就是我们的目标对象ContextRefresher,它是用来触发刷新过程的对象。
RefreshScope源码
public class RefreshScope extends GenericScope implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, Ordered {
//可以看到它继承了GenericScope和实现了三个接口,分别获取context用、接收容器刷新事件能力和排序接口的功能
}
RefreshScope刷新入口
就是ContextRefresher得refresh()方法
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
//刷新refreshScope里面得bean实例,具体流程看下面代码
this.scope.refreshAll();
return keys;
}
refreshEnvironment()源码
//org.springframework.cloud.context.refresh.ContextRefresher#refreshEnvironment
//抽取出除了SYSTEM,JNDI,SERVLET)之外所有参数变量
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
//把原来得environment里面得参数放到一个新建得Spring Context容器下重新加载,完事之后关闭新容器,这里就是获取参数得新值了。
addConfigFilesToEnvironment();
//获取新的参数值,并和之前得进行比较找出改变得参数值
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
//发布环境变更事件,并带上改变得参数值
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll
public void refreshAll() {
//调用父类GenericScope得destroy方法,清除Scope里面得缓存,下次就会重新从BeanFactory获取一个新的实例(该实例使用新的配置)
super.destroy();
//发布相关事件
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
总结
ContextRefresher 是用来刷新容器中标记了@RefreshScope Bean的类,它的refresh()方法可以实现这一功能。大概流程就是刷新当前容器的environment,删除掉Scope中的实例,然后在获取实例时从小建立bean,这时候新的enviroment已经生成,就拿到了最新的配置。