springboot源码解读之RefreshScope动态刷新配置

目录

 

scope原理

从容器中获取scope bean

负责创建scope bean的ScopedProxyFactoryBean

RefreshScope

RefreshScope缓存清理

配置重新加载


scope原理

从容器中获取scope bean

scope是spring framework中的概念,对于singleton和prototype对象的获取在beanFactory中直接实现,其他scope对象的获取则委托给scope自己。当然,bean的实例化、依赖注入还得依靠beanFactory,scope只是控制bean的生命周期,决定什么时候重新创建bean。

具体的逻辑在AbstractBeanFactory的doGetBean方法:

protected <T> T doGetBean(...){
    //...
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    if (mbd.isSingleton()) {
        //...
    } else if (mbd.isPrototype())
       //...
    } else {
        String scopeName = mbd.getScope();
        final Scope scope = this.scopes.get(scopeName);
        Object scopedInstance = scope.get(beanName, () -> {			
			return createBean(beanName, mbd, args);	
		});
    }
    //...
}

负责创建scope bean的ScopedProxyFactoryBean

一个正常bean A注入到令一个bean B后,B保持A的引用不会再改变。对于非单例的bean,每次调用它的时候都有可能要获取一个新的bean实例。所以不能将scope对象之间注入到其他bean中,而是要注入一个代理,每当代理被调用时再获取真正的bean实例执行相应的方法,这时有可能使用之前创建好的实例也有可能重新创建一个新实例。

如果一个bean被@scope标注,则需要对它的BeanDefinition做设置scope名字、配置对应的FacoryBean等特殊处理,相关逻辑见ClassPathBeanDefinitionScanner的doScan方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			//...
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				// 创建了另外一个新的ScopedProxyFactoryBean的BeanDefinition
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

BeanDefinition经过AnnotationConfigUtils.applyScopedProxyMode的处理会为负责创建代理对象的ScopedProxyFactoryBean创建一个新的BeanDefinition,并使用scope bean的名字注册到BeanFactory中。ScopedProxyFactoryBean就是负责创建scope bean的FactoryBean。

scope bean本来的BeanDefinition也注册到BeanFactory中,使用的名字是在原名字前加上scopedTarget.前缀。

BeanDefinition相关处理逻辑见ScopedProxyUtils:

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
		BeanDefinitionRegistry registry, boolean proxyTargetClass) {

	String originalBeanName = definition.getBeanName();
	BeanDefinition targetDefinition = definition.getBeanDefinition();
	String targetBeanName = getTargetBeanName(originalBeanName);

	
	RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
	proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
	proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
	//...

	// 原来的BeanDefinition在这里注册,名字前面加上了scopedTarget.
	registry.registerBeanDefinition(targetBeanName, targetDefinition);

	return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}

FactoryBean本身作为单例bean,在容器refresh单例创建的时候,发现一个bean是FactoryBean则会在beanName前加上&前缀调用getBean完成FactoryBean的创建。

而ScopedProxyFactoryBean实现了BeanFactoryAware接口,它的实例创建之后执行initializeBean,其中在setBeanFactory的时候创建了代理对象并保存在了ScopedProxyFactoryBean。

public void setBeanFactory(BeanFactory beanFactory) {
   //...
   ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
   this.scopedTargetSource.setBeanFactory(beanFactory);
   ProxyFactory pf = new ProxyFactory();
   //... ​
   this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}

其他bean依赖注入scope bean的时候会先查出FactoryBean,然后从FactoryBean获取到代理。

public Object getObject() {
	if (this.proxy == null) {
		throw new FactoryBeanNotInitializedException();
	}
	return this.proxy;
}

代理执行相关方法的时候会调用getBean(scopedTarget.beanName)获取真正scope bean的实例,这个时候的beanName有一个scopedTarget.前缀。有前缀的beanName获取到的是真正的scope bean的实例,不带前缀获取到的是代理对象实例,带取址符&前缀则获取到FactoryBean。

RefreshScope

以上说的都是spring中原有的功能,RefreshScope则是springcloud中scope的一种实现,它的名字就是refresh,所以它负责加了@RefreshScope注解的bean的创建。

RefreshScope在RefreshAutoConfiguration中被声明,作为BeanDefinitionRegistryPostProcessor被提前初始化,并作为scope把自己注册到beanFactory中。注册的逻辑在父类GenericScope中:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
		throws BeansException {
	this.beanFactory = beanFactory;
	beanFactory.registerScope(this.name, this);
	setSerializationId(beanFactory);
}

RefreshScope是GenericScope的子类,主要的逻辑都在GenericScope中实现。

public class GenericScope implements Scope, BeanFactoryPostProcessor,
		BeanDefinitionRegistryPostProcessor, DisposableBean {
	// ...
	private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
			new StandardScopeCache());
	// ...
}

GenericScope的内部维护一个cache,每当BeanFactory来获取对象的时候都会先从缓存中获取。所以,每次配置更新时只要清除缓存,下次获取对象的时候就会重新创建新对象,为新对象注入最新属性,这样就实现了RefreshScope的语义。

public Object get(String name, ObjectFactory<?> objectFactory) {
	BeanLifecycleWrapper value = this.cache.put(name,
			new BeanLifecycleWrapper(name, objectFactory));
	this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
	try {
		return value.getBean();
	}
	catch (RuntimeException e) {
		this.errors.put(name, e);
		throw e;
	}
}

上面代码中虽然看似每次都新创建一个对象放入缓存中,实际上是创建了一个objectFactory的封装对象,并没有真正创建对象。而cache的put逻辑最终实现为map的putIfAbsent,即缓存中已存在key则返回原来的value。见StandardScopeCache:

public class StandardScopeCache implements ScopeCache {

	private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<String, Object>();	
	// ...
	public Object put(String name, Object value) {
		Object result = this.cache.putIfAbsent(name, value);
		if (result != null) {
			return result;
		}
		return value;
	}
}

RefreshScope缓存清理

上面说到配置更新后需要清除RefreshScope中的缓存,ContextRefresher负责完成这一任务。它由RefreshAutoConfiguration引入,创建的时候会自动注入RefreshScope和context。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
		matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {	

	@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);
	}
    // ...
}

ContextRefresher的refresh方法就是清理RefreshScope缓存的入口

public synchronized Set<String> refresh() {
	Set<String> keys = refreshEnvironment();
	this.scope.refreshAll();
	return keys;
}

ContextRefresher里的scope就是RefreshScope,它的refreshAll最终会落实到到GenericScope的destroy方法,其中清理了所有的缓存。

public void destroy() {
	//...
	Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
	//...
}

配置重新加载

要想动态刷新配置,光清除RefreshScope的缓存还不够,还要具备重新加载配置到context中的能力,这一任务也是ContextRefresher完成的。实际上就是在refresh方法中清理RefreshScope缓存之前,即refreshEnvironment方法中完成了配置的重新加载。

public synchronized Set<String> refreshEnvironment() {
	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(this.context, keys));
	return keys;
}

跟springcloud的启动非常相似,也是用SpringApplication新创建了一个context,新的context加载了最新的配置,然后再合并到原来的context中。因为构建SpringApplication时添加了BootstrapApplicationListener,所以PropertySourceLocator会重新执行。如果PropertySourceLocator里面实现了从数据库或其他网址加载配置,则现在已经加载了最新的配置到context中。

结合上面的RefreshScope缓存的清理,当RefreshScope bean下次被调用的时候就已经是最新的配置了。

ConfigurableApplicationContext addConfigFilesToEnvironment() {
	ConfigurableApplicationContext capture = null;
	try {
		StandardEnvironment environment = copyEnvironment(
				this.context.getEnvironment());
		SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).environment(environment)...;			
		builder.application()
				.setListeners(Arrays.asList(new BootstrapApplicationListener(),
						new ConfigFileApplicationListener()));
		capture = builder.run();
		//...
		MutablePropertySources target = this.context.getEnvironment()
				.getPropertySources();
		String targetName = null;
		for (PropertySource<?> source : environment.getPropertySources()) {
			String name = source.getName();
			if (target.contains(name)) {
				targetName = name;
			}
			if (!this.standardSources.contains(name)) {
				if (target.contains(name)) {
					target.replace(name, source);
				}
				else {
					if (targetName != null) {
						target.addAfter(targetName, source);
					}
					else {
						// targetName was null so we are at the start of the list
						target.addFirst(source);
						targetName = name;
					}
				}
			}
		}
	}
	
	return capture;
}

结合上面的RefreshScope缓存的清理和配置重新加载,当RefreshScope bean下次被调用的时候就已经是最新的配置了。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值