Spring-Cloud源码:@RefreshScope

文章目录

  • 第一部分:@RefreshScope生成代理
    • 1. @RefreshScope注解
    • 2. 扫描@RefreshScope注解
    • 3. AnnotationConfigUtils.applyScopedProxyMode
    • 4. ScopedProxyCreator.createScopedProxy
    • 5. ScopedProxyFactoryBean
      • 5.1 `#getObject`
      • 5.2 `#setBeanFactory`
      • 5.3 LockedScopedProxyFactoryBean
      • 5.4 LockedScopedProxyFactoryBean#setBeanFactory
      • 5.5 LockedScopedProxyFactoryBean#invoke
    • 6. 小结
  • 第二部分:@RefreshScope的原始bean是如何刷新的
    • 1. IOC容器获取Scope类型的Bean
    • 2. RefreshScope
    • 3. BeanLifecycleWrapper
        • 3.1 BeanLifecycleWrapper的成员属性
        • 3.2 getBean方法
    • 4. desotry方法
    • 5. 小结
  • 第三部分:如何触发动态刷新
    • 1. RefreshEventListener
    • 2. 如何刷新Environment
      • 2.1 ContextRefresher#refresh
      • 2.2 ContextRefresher#refreshEnvironment
      • 2.3 ContextRefresher#addConfigFilesToEnvironment
    • 3. 谁发布了RefreshEvent事件
      • 3.1 以Nacos为例
  • 总结

本文并不打算讲述@RefreshScope注解如何使用。相反,我们将关注使用@RefreshScope注解的实现原理

第一部分:@RefreshScope生成代理

前置知识

  1. @RefreshScope注解标记的class,最终会向容器中注入两个BeanDefinition,一个是原始class对应的BeanDefinition,另一个是原始class对应的代理的BeanDefinition
  2. 原始class对应的BeanDefinition会被设置为不可注入,假设class名为Person,当Person被依赖注入到别的对象时,实际上注入的是代理对象
  3. 执行代理对象的任意方法时,代理对象通过beanFactory去获取Person未被代理的bean ( 也就是那个不可被注入的BeanDefinition对应的bean ),通过bean去执行方法。
    相当于代理是一个委托人,所有方法的执行都委托给:1. spring容器获取到bean 2. 通过bean去执行方法
  4. 重点就在于:每次执行方法时的bean都是从spring容器中重新获取的

1. @RefreshScope注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

可以看到,@RefreshScope注解上使用了@Scope注解, 对应的value为"refresh"

如此,一旦某个类被@RefreshScope所标注,spring扫描到这个类时,就可以扫描到@Scope注解,并且得到value为"refresh"

2. 扫描@RefreshScope注解

=================================================================

跟踪扫描注解的代码前,先有一个认知

Spring在扫描到被@Scope标注的类时, 会生成一个额外的 BeanDefinition

这个BeanDefinition的beanClass为ScopedProxyFactoryBean

=================================================================

doScan方法位于ClassPathBeanDefinitionScanner类中,Spring基于backPackages扫描组件就是通过这个方法实现的

	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		
		for (String basePackage : basePackages) {
			
			// 1. 扫描后得到 { BeanDefinition } 集合
			// 具体的扫描逻辑在 { findCandidateComponents } 方法中, 这里就不深究了
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			
			for (BeanDefinition candidate : candidates) {
				
				// 2. 扫描 { BeanDefinition  } 上的 { @Scope } 注解
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				
				// { scopeMetadata.getScopeName() } 就是 { @Scope.value() }
				candidate.setScope(scopeMetadata.getScopeName());
				
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				
				// 3. 核心方法, 这个方法里, spring创建了另一个 { BeanDefinition } 并且注入到容器中
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
						
				beanDefinitions.add(definitionHolder);
				
				// 4. 将 { AnnotationConfigUtils.applyScopedProxyMode } 方法
				// 返回的 { BeanDefinition } 注入到容器中
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
		
		return beanDefinitions;
	}

下面继续跟踪AnnotationConfigUtils.applyScopedProxyMode

3. AnnotationConfigUtils.applyScopedProxyMode

	static BeanDefinitionHolder applyScopedProxyMode(
			ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
	
		// 1. 获取 { proxyMode }
		// 无论是 { @RefreshScope } 还是 { @Scope } 注解都有一个 { proxyMode() } 属性
		// 对应的类型就是 { ScopedProxyMode }  , 默认值为 { TARGET_CLASS }
		ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
		
		// 2. 如果手动设置为NO了, 那么不启用代理, 这种case很少见
		if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
			return definition;
		}
		
		boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
		
		// 3. 转而调用 { ScopedProxyCreator.createScopedProxy }
		return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
	}

下面继续跟踪ScopedProxyCreator.createScopedProxy

4. ScopedProxyCreator.createScopedProxy

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

		return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass);
	}

没做啥事,直接调用了ScopedProxyUtils.createScopedProxy

ScopedProxyUtils.createScopedProxy是一个非常核心的方法,这个方法主要做了三件事

  1. 创建代理的BeanDefinition
  2. 将原始的BeanDefinition设置为不可注入
  3. 注册代理的BeanDefinition到Spring容器中。在Spring的下一个生命周期,这些BeanDefinition将被创建为Bean
	public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
			BeanDefinitionRegistry registry, boolean proxyTargetClass) {

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

		// 1. 创建代理的 { BeanDefinition }, 对应的beanClass为 { ScopedProxyFactoryBean.class }
		RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
		proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
		proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
		proxyDefinition.setSource(definition.getSource());
		proxyDefinition.setRole(targetDefinition.getRole());
		
		// 2. 设置 { targetBeanName }, 为原始 { BeanDefinition } 的 beanName
		
		// !!!很重要,因为到时候需要基于 { targetBeanName } 从spring容器中获取原始的bean
		proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
		
		if (proxyTargetClass) {
			targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
		}
		else {
			proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
		}

		proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
		proxyDefinition.setPrimary(targetDefinition.isPrimary());
		
		if (targetDefinition instanceof AbstractBeanDefinition) {
			proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
		}

		// 3. 为原始 { BeanDefinition } 设置为不可注入
		// 如此, 这个 { BeanDefinition } 创建出来的bean就无法被依赖注入了
		targetDefinition.setAutowireCandidate(false);
		targetDefinition.setPrimary(false);

		// 4. 注册原始的 { BeanDefinition } 到IOC容器中
		registry.registerBeanDefinition(targetBeanName, targetDefinition);
	
		// 5. 将 { proxyBeanDefinition } 包装成 { BeanDefinitionHolder }
		// 在第二步的 { doScan } 方法中, 会将这里 return的 { proxyBeanDefinition } 注册到IOC容器中
		return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
	}

经历以上步骤后,每个被@RefrehScope注解标注的class,会生成两个BeanDefinition注册到容器中

假设有一个Person的class

@RefreshScope
@Component
class Person {
	// ...
}

当这个class被扫描后,就会出现如下图的情况

Spring-Cloud源码:@RefreshScope_第1张图片

Spring容器的下一个生命周期,就会将容器中的BeanDefinition初始化为Bean

接下来看看ScopedProxyFactoryBean的逻辑

5. ScopedProxyFactoryBean

Spring-Cloud源码:@RefreshScope_第2张图片

  • ScopedProxyFactoryBean是一个FactoryBean, Spring通过调用FactoryBeangetObject()方法获取bean
  • ScopedProxyFactoryBean还实现了BeanFactoryAware接口,那么Spring将会回调setBeanFactory方法,并且传递BeanFactory作为参数

5.1 #getObject

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

getObject()方法直接将proxy属性返回,而proxy属性在setBeanFactory方法中初始化

5.2 #setBeanFactory

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
	
		ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
		
		// 1. 给 { scopedTargetSource } 赋值 { beanFactory }
		this.scopedTargetSource.setBeanFactory(beanFactory);

		ProxyFactory pf = new ProxyFactory();
		pf.copyFrom(this);
		
		// 2. 给 { proxyFactory } 赋值 { targetSource }
		pf.setTargetSource(this.scopedTargetSource);

		Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");

		ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
		
		pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

		pf.addInterface(AopInfrastructureBean.class);
		
		// 3. 创建代理
		this.proxy = pf.getProxy(cbf.getBeanClassLoader());
	}

这里又使用到了ProxyFactory 来创建代理

  • 关于ProxyFactory 的使用,可以查看这篇文章ProxyFactory使用
  • 关于ProxyFactory 的源码,可以查看这篇文章ProxyFactory源码

注释1和注释2,一直在提及scopedTargetSource

scopedTargetSource ScopedProxyFactoryBean的成员属性

对应的class为SimpleBeanTargetSource

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

	@Override
	public Object getTarget() throws Exception {
		return getBeanFactory().getBean(getTargetBeanName());
	}

}

只需要关注getTarget方法,其逻辑为:每次都从beanFactory中根据beanName获取bean

那么这个beanName是什么呢?

还记得在ScopedProxyUtils.createScopedProxy方法中给ScopedProxyFactoryBeanBeanDefinition中设置了beanName

跳转回顾ScopedProxyUtils.createScopedProxy方法

  1. scopedTargetSource 被赋值给了proxyFactory
  2. ProxyFactory创建出来的代理对象实现了Adviced接口
  3. Adviced接口就具有获取targetSource的能力
  4. 在接下来:LockedScopedProxyFactoryBean就会将代理对象转换为Adviced, 然后获取targetSource,实际上就是scopedTargetSource
  5. 再调用targetSourcegetTarget()方法,根据上面的代码可知,getTarget()方法将从Spring容器中获取bean,这一点非常重要,暂且有个印象

5.3 LockedScopedProxyFactoryBean

GenericScopeRefreshScope的父类

GenericScope同时还是一个BeanDefinitionRegistryPostProcessor

Spring会回调GenericScopepostProcessBeanDefinitionRegistry方法

GenericScopepostProcessBeanDefinitionRegistryScopedProxyFactoryBeanBeanDefinition替换为了LockedScopedProxyFactoryBean

LockedScopedProxyFactoryBeanScopedProxyFactoryBean的子类

这块内容本应该在下面提及,但是LockedScopedProxyFactoryBean重写了ScopedProxyFactoryBeansetBeanFactory方法

Spring-Cloud源码:@RefreshScope_第3张图片

5.4 LockedScopedProxyFactoryBean#setBeanFactory

@Override
public void setBeanFactory(BeanFactory beanFactory) {
	
	// 1. 先调用父类的 { setBeanFactory }
	super.setBeanFactory(beanFactory);
	
	// 2. 获取proxy对象
	Object proxy = getObject();
	
	// 3. 强制转换为 { Advised } , 并且添加一个 "advice" { this }
	if (proxy instanceof Advised) {
		Advised advised = (Advised) proxy;
		
		// 插入在索引为0的位置
		advised.addAdvice(0, this);
	}
}

LockedScopedProxyFactoryBean实现了MethodInterceptor方法,是一个Advice

那么,执行代理对象的任何方法时,都会首先执行LockedScopedProxyFactoryBeaninvoke方法

5.5 LockedScopedProxyFactoryBean#invoke

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
	Method method = invocation.getMethod();
	
	// equals / hashCode / toString ... 直接执行方法
	if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method)
			|| AopUtils.isHashCodeMethod(method)
			|| isScopedObjectGetTargetObject(method)) {
		return invocation.proceed();
	}

	// 1. 获取代理对象
	Object proxy = getObject();
	
	// 2. 获取读写锁, 读写锁的创建逻辑会在分析 { RefreshScope } 的源码时提及
	ReadWriteLock readWriteLock = this.scope.getLock(this.targetBeanName);
	
	Lock lock = readWriteLock.readLock();
	
	// 3. 上读锁
	lock.lock();
	
	try {
		// 4. 强制转换为 { adviced }
		Advised advised = (Advised) proxy;
		
		ReflectionUtils.makeAccessible(method);
		
		// 5. 反射执行方法
		
		// 第一个参数:要执行的方法
	
		// 第二个参数:{ dvised.getTargetSource().getTarget() } 这里很关键
		// 还记得 5.2 分析 { setBeanFactory } 方法创建代理时提及的吗
		// 这里的 { getTarget } 方法会从spring容器中获取bean
		
		// 第三个参数:方法参数
		return ReflectionUtils.invokeMethod(method,
				advised.getTargetSource().getTarget(),
				invocation.getArguments());
	}
	catch (UndeclaredThrowableException e) {
		throw e.getUndeclaredThrowable();
	}
	finally {
		lock.unlock();
	}
}

跳转回顾getTarget()方法

6. 小结

1~5步,主要分析了以下逻辑

  1. @Refresh注解标注的class被Spring扫描后,会向容器中注入两个BeanDefinition,一个是原始的Bean的BeanDefinition,另一个是ScopedProxyFactoryBeanBeanDefinition
  2. 原始的bean被设置为不可注入ScopedProxyFactoryBean被赋值targetBeanName属性,对应原始的Bean的BeanName ,使ScopedProxyFactoryBean具有从Spring容器中根据BeanName获取原始Bean的能力
  3. RefreshScope会在Spring容器创建Bean之前,将容器中BeanDefinitionScopedProxyFactoryBean替换为LockedScopedProxyFactoryBean
  4. LockedScopedProxyFactoryBean是一个FactoryBean,其getObject方法会返回一个代理。假设有一个Person类使用了@RefreshScope注解,另外一个类依赖Spring注入Person类的实例,实际上注入的是getObject方法返回的代理对象
  5. 执行代理对象的任何方法时,代理对象都会从Spring容器中根据BeanName获取原始Bean来执行

关键点就在于,每次执行方法都是从Spring容器中获取的bean来执行方法

Spring-Cloud源码:@RefreshScope_第4张图片

第二部分:@RefreshScope的原始bean是如何刷新的

1. IOC容器获取Scope类型的Bean

我们已经知道了实际上注入的是代理对象,而代理对象会通过IOC容器获取原始bean去执行方法

根据第一部分扫描Bean时可知,原始Bean是一个Scope类型的Bean

Spring容器中只有单例的Bean会被缓存!!!
Spring容器中只有单例的Bean会被缓存!!!
Spring容器中只有单例的Bean会被缓存!!!

也就是说,代理对象每次获取原始的Bean,都会执行下面的逻辑

Spring-Cloud源码:@RefreshScope_第5张图片

if (mbd.isSingleton()) {
	// 获取单例Bean
	// ...
}
else if (mbd.isPrototype()) {
	// 获取多例Bean
	// ...
}

else {
	// 1. 获取 { beanDefinition } 的 { scope } 属性
	String scopeName = mbd.getScope();
	
	// 2. 根据 { scope } 属性找到对应的 { Scope } 对象
	
	// 例如 scope = "refresh"对应的就是 { RefreshScope }

	final Scope scope = this.scopes.get(scopeName);

	// 3. 调用 { scope }	 对象的 { get } 方法创建bean
	
	// 第一个参数是 { beanName } : bean的名称, 
	
	// !!!第二个参数是 { ObjectFactory } : bean创建工厂, 拥有创建bean的能力
	
	Object scopedInstance = scope.get(beanName, () -> {
		beforePrototypeCreation(beanName);
		try {
			return createBean(beanName, mbd, args);
		}
		finally {
			afterPrototypeCreation(beanName);
		}
	});
	bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}

Spring根据BeanDefinition创建对象时

如果BeanDenifition既不是单例也不是多态的,那么根据BeanDenifitionscope属性来创建对象

@Scope(value = "refresh") , 对应的 Scope 就是 RefreshScope

当前RefreshScope指定是类,而不是注解

也就是说,Spring将创建bean委托给了RefreshScope

2. RefreshScope

根据上一步的分析,Spring调用了Scope对象的get(beanName, obejctFactory)方法

这个方法位于RefreshScope的父类GenericScope

RefreshScope并没有重写这个方法,所以跟踪父类的get方法

	public Object get(String name, ObjectFactory<?> objectFactory) {
		
		// 1. 创建BeanLifecycleWrapper 
		BeanLifecycleWrapper value = new BeanLifecycleWrapper(name, objectFactory);
		
		// 2. 放入cache中
		this.cache.put(name, value);
		
		// 3. 每个 { beanName } 都有一把读写锁
		
		// 在Locked
		this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
		try {
			// 4. 调用 { BeanLifecycleWrapper#getBean } 方法, 创建一个对象
			return value.getBean();
		}
		catch (RuntimeException e) {
			this.errors.put(name, e);
			throw e;
		}
	}
	@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		
		// 1. 创建BeanLifecycleWrapper, 并且放入缓存中
		
		// 这个put方法被封装了一层, 其实里面调用的是 { putIfAbsent }
		
		// 也就是说, 只有cache不存在的时候才会put进去
	
		// Spring其实这里写的不太好, 每次都new了一个新的 { BeanLifecycleWrapper }, 却不一定能用上
		BeanLifecycleWrapper value = this.cache.put(name,
				new BeanLifecycleWrapper(name, objectFactory));
		
		// 2. 每个 { beanName } 都有一把读写锁
		// 在 { LockedScopedProxyFactoryBean } 的 { invoke } 方法中就使用了读锁
		this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
		
		try {
			// 3. 调用 { BeanLifecycleWrapper#getBean } 方法, 创建一个对象
			return value.getBean();
		}
		catch (RuntimeException e) {
			this.errors.put(name, e);
			throw e;
		}
	}

值得注意的是:

RefreshScope对象是单例的,也就是说,所有scoperefresh的对象

都是由RefreshScope对象创建的,那么也就全都保存在了RefreshScope对象的cache属性中

可以看到,RefreshScope做了这些事情

  1. 创建BeanLifecycleWrapper
  2. 保存cache
  3. beanName创建ReentrantReadWriteLock
  4. 调用BeanLifecycleWrappergetBean方法

继续跟踪BeanLifecycleWrapper

3. BeanLifecycleWrapper

3.1 BeanLifecycleWrapper的成员属性
class BeanLifecycleWrapper {
	
	// 1. beanName
	private final String name;
	
	// 2. bean创建工厂, 就是步骤二调用 { get } 方法时的第二个参数
	private final ObjectFactory<?> objectFactory;
		
	// 3. bean的实例, 通过 { objectFactory } 创建得到, 一开始为 { null }
	private Object bean;
	
	// 4. bean销毁前的回调, 如果为 { null } , 那么bean无需被销毁
	private Runnable callback;
	
}
3.2 getBean方法
public Object getBean() {
	if (this.bean == null) {
		// 双重检测锁
		synchronized (this.name) {
			if (this.bean == null) {
				
				// 调用objectFactory的getObject()方法创建bean
				this.bean = this.objectFactory.getObject();
			}
		}
	}
	return this.bean;
}
  1. 就是调用了objectFactorygetBean方法
    objectFactory就是IOC容器创建bean时的第二个参数,跳转回顾第二个参数

  2. 将对象赋值给this.bean属性,下次再调用getBean()方法时直接返回this.bean

Spring-Cloud源码:@RefreshScope_第6张图片

那么如果,将某个BeanName在Cache中的BeanLifecycleWrapper移除掉

下次再调用getBean方法时,这个对象就会通过objectFactory#getObject重新创建一次

所以,GenericScope有一个destroy方法,就可以清除缓存

4. desotry方法

	@Override
	public void destroy() {
		List<Throwable> errors = new ArrayList<Throwable>();
		
		// 看这里:清空缓存
		Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
		
		for (BeanLifecycleWrapper wrapper : wrappers) {
			try {
				Lock lock = this.locks.get(wrapper.getName()).writeLock();
				lock.lock();
				try {
					wrapper.destroy();
				}
				finally {
					lock.unlock();
				}
			}
			catch (RuntimeException e) {
				errors.add(e);
			}
		}
		if (!errors.isEmpty()) {
			throw wrapIfNecessary(errors.get(0));
		}
		this.errors.clear();
	}

那我们接下来应该去寻找哪里调用了destory方法,应该就是动态刷新的入口点了

5. 小结

  1. 已知代理对象每次执行方法时,都会从Spring容器中获取原始Bean
  2. 原始Bean属于Scope类型,并不会被缓存,每次都会调用RefreshScope对象的get方法获取
  3. RefreshScope 有一个cache属性,Refresh使用cache通过beanName获取BeanLifecycleWrapper,再调用BeanLifecycleWrapper的getBean方法获取对象
  4. 一旦RefreshScopecache被清除,再次获取bean的时候,无法命中缓存,就会重新创建BeanLifecycleWrapper,也就会再次调用objectFactory#getObject创建Bean
  5. objectFactory#getObject创建Bean的时候会使用Spring的Environment中的属性为Bean的属性赋值

第三部分:如何触发动态刷新

1. RefreshEventListener

RefreshEventListener是一个ApplicationListener

一旦接收到RefreshEvent,就会调用ContextRefresherrefresh方法

ContextRefresher先重新刷新上下文,再调用RefreshScoperefreshAll方法

refreshAll方法中调用了destory方法:清空缓存
Spring-Cloud源码:@RefreshScope_第7张图片

那么就还剩下了最后两个疑问

  1. ContextRefresher是如何刷新environment
  2. 是谁发布了RefreshEvent事件

2. 如何刷新Environment

2.1 ContextRefresher#refresh

public synchronized Set<String> refresh() {

	// 刷新environment
	Set<String> keys = refreshEnvironment();
	
	// this.scope 就是 refreshScope
	this.scope.refreshAll();
	                                                                                                                       
	return keys;
}

2.2 ContextRefresher#refreshEnvironment

public synchronized Set<String> refreshEnvironment() {
	
	// 忽略:为EnvironmentChangeEvent做准备的
	Map<String, Object> before = extract(
			this.context.getEnvironment().getPropertySources());
	
	// 核心方法:就是在这里更新了ApplicationContext的environment属性
	addConfigFilesToEnvironment();
	
	Set<String> keys = changes(before,
			extract(this.context.getEnvironment().getPropertySources())).keySet();
	
	this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
	
	return keys;
}

2.3 ContextRefresher#addConfigFilesToEnvironment

	/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {
		ConfigurableApplicationContext capture = null;
		
		// 1. 保存当前的environment
		StandardEnvironment environment = copyEnvironment(
				this.context.getEnvironment());
	
		// 2. 创建一个SpringApplicationBuilder, 用于创建SpringApplication
		SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)
				.bannerMode(Mode.OFF).web(WebApplicationType.NONE)
				// !!! 设置environment
				.environment(environment);
		
		// 3. 
		// - 添加BootstrapApplicationListener读取bootstrap.yml
		// - 添加ConfigFileApplicationListener读取application.yml
		builder.application()
				.setListeners(Arrays.asList(new BootstrapApplicationListener(),
						new ConfigFileApplicationListener()));
		
		// 4. 启动SpringBootApplication
		// 启动完成后,  这个environment对象就有最新的值了
		capture = builder.run();
		
		// 5. target是当前contenxt的environment
		MutablePropertySources target = this.context.getEnvironment()
				.getPropertySources();
				
		String targetName = null;
		
		// 6. 进行比较
		for (PropertySource<?> source : environment.getPropertySources()) {
			String name = source.getName();
			
			if (target.contains(name)) {
				targetName = name;
			}
			
			// standardSources包含了一些不需要更新的属性集合
			if (!this.standardSources.contains(name)) {
				
				// 当前context有这个key对应的值, 那么替换为最新的
				if (target.contains(name)) {
					target.replace(name, source);
				}
				else {
					// 没有则新增
					if (targetName != null) {
						target.addAfter(targetName, source);
					}
					else {
						target.addFirst(source);
						targetName = name;
					}
				}
			}
		}
		return capture;
	}

为什么重新启动一个SpringBootApplication,Environment里就有最新的值了呢?

以Nacos为例

PropertySourceBootstrapConfiguration位于spring.factories文件中,会被SpringBootApplication加载到

通过SpringBoot回调initializeinitialize通过PropertySourceLocator加载属性,放入到environment中

而Nacos的AutoConfiguration则注入了NacosPropertySourceLocator到容器中

NacosPropertySourceLocator通过网络链接到nacos,读取配置信息,最终被放入到了environment中

这一块属于nacos-config的实现细节,本篇就不深究了

=====================================================================

只需要知道一个结论

重新启动的SpringBootApplication的environment具有最新的属性,ContextRefresher进行比较后,给当前context的environment赋最新的值

=====================================================================

3. 谁发布了RefreshEvent事件

3.1 以Nacos为例

NacosContextRefresher#registerNacosListener方法中

	private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		
		// 监听器, configServer长轮询nacos-server
		// 一旦监听到配置变更, 就会回调listener的 { innerReceive } 方法
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);

						// !!! 发布RefreshEvent事件
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));

					}
				});
		try {
			
			// 注册listener, 启用长轮询监听
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
			log.warn(String.format(
					"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
					groupKey), e);
		}
	}

这块属于Nacos-Config的源码,就不深究了

总结

  1. @RefreshScope注解标注的class,会生成两个BeanDefinition
    • 一个是LockedScopedProxyFactoryBean :用于生成代理对象
    • 一个是class对应的BeanDefinition,我们暂成为原始BeanDefinition :用于生成原始Bean
  2. 原始Bean是不可被注入的,实际上注入的都是代理Bean
  3. 代理Bean执行方法时,都是通过applicationContext获取原始Bean来执行的
  4. 原始Bean是一个Scope类型的Bean,applicationContext通过RefreshScope对象来获取原型Bean
  5. RefreshScope具有缓存机制
  6. 一旦缓存被清除,原始Bean就会重新被创建
  7. 一旦发布了RefreshEvent事件
    1. ContextRefresh就会通过创建一个新的SpringApplication来更新environment,赋最新值
    2. ContextRefresh调用RefreshScope清空缓存

你可能感兴趣的:(java,spring,spring,cloud)