Spring学习之ContextRefreshr

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已经生成,就拿到了最新的配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值