开发者

SpringCloud @RefreshScope注解源码层面深入分析

目录
  • 写在前面
  • 正文

写在前面

最近在研究Spring Cloud和Spring Cloud Alibaba源码,在看到Nacos的配置中心的时候,有注意到自动刷新配置的玩法,底层实现依靠@RefreshScope注解。那么为什么要写这篇文章呢?笔者认为@RefreshScope注解源码实现跨度特别大,从Spring Cloud Alibaba 到Spring Cloud 到 Spring Boot 再到Spring,笔者认为能够理解它的源码实现的话对Spring全家桶的理解又能上升一个档次~

正文

版本如果下:

Spring:5.3.23

Spring Boot:2.6.3

Spring Cloud:3.1.4

Spring Cloud Alibaba:2021.0.4.0

Nacos:2.0.4

先会用,再深入源码,所以我们需要从案例出发。

@RestController
@RefreshScope
public class ConsumerController {
    @Value("${consumer.value:moren}")
    private String value;
    @RequestMapping("/consumer")
    public String consumer(){
        return value;
    }
}

SpringCloud @RefreshScope注解源码层面深入分析

SpringCloud @RefreshScope注解源码层面深入分析

能够正常使用Nacos服务端的配置数据,所以,我们发现在类上存在@RefreshScope注解,所以我们的重心看往@RefreshScope注解。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
	/**
	 * @see Scope#proxyMode()
	 * @return proxy mode
	 */
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

此时,很清楚的看到@RefreshScope注解聚合了@Scope注解,并且赋予了"refresh". 此时,从Spring惯用玩法,我们需要找到Spring何时解析的@Scope注解,何时解析的@RefreshScope注解。

protected Set<BeanDefinitionHolder> DOScan(String... basePackages) {
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {   
                // 解析当前类上是否存在@scope注解
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				…………
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    // 生成Scope代理类
					definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
	@Override
	public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
		if (definition instanceof AnnotatedBeanDefinition) {
			AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
			AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
					annDef.getMetadata(), this.scopeAnnotationType);
		}
		return metadata;
	}
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;

上面代码,是解析@ComponentScan时,扫描指定路径包的@Component注解类,而我们的@RestController也是一个@Component,并且我们的@RestController类上还有@RefreshScope注解,而@RefreshScope注解又存在@Scope注解。所以,接下来我们分析如何做Scope的代理。

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
			BeanDefinitionRegistry registry, boolean proxyTargetClass) {
        // 拿到原类的名字,和对应的BeanDefinition
		String originalBeanName = definition.getBeanName();
		BeanDefinition targetDefinition = definition.getBeanDefinition();
		String targetBeanName = getTargetBeanName(originalBeanName);
        // 手动创建Scope代理类的BeanDefinition
        // 设置BeanDefinition的BeanClass为ScopedProxyFactoryBean
		RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
		proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
		proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
		proxyDefinition.setSource(definition.getSource());
		proxyDefinition.setRole(targetDefinition.getRole());
        …………
        // 将代理类设置为自动注入,并且优先级最高。        
        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
		proxyDefinition.setPrimary(targetDefinition.isPrimary());
		// 将原类设置为不能自动注入
		targetDefinition.setAutowireCandidate(false);
		targetDefinition.setPrimary(false);
		// 将原类的BeanDefinition注册到工厂中
		registry.registerBeanDefinition(targetBeanName, targetDefinition);
		return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
	}
public static String getTargetBeanName(String originalBeanName) {
	return TARGET_NAME_PREFIX + originalBeanName;
}
private static final String TARGET_NAME_PREFIX = "scopedTarget.";

这里就是非常关键的部分, 有没有感觉是在"偷天换日"。手动创建BeanDefintion,把ScopedProxyFactoryBean作为BeanClass,并且把原类的name作为手动创建BeanDefintion的name。把原类的BeanDefinition的name加上前缀scopedTarget. 相信Spring底子好的读者很容易看明白。下面是改变的流程图。

SpringCloud @RefreshScope注解源码层面深入分析

那么,说了这么多的意义在哪里呢?我们的主题不是动态刷新么,怎么连注册中心的影子都没见到?年轻人,不要着急,文章开头就说了本篇文章跨度很大~

此时,我们需要从Spring Cloud规范包入手,相信各位读者知道,Spring-Cloud-Commons和Spring-Cloud-Context 这两个Spring Cloud规范包。从他们的spring.factories自动装配包可以得知以下信息。

SpringCloud @RefreshScope注解源码层面深入分析

@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
	return new RefreshScope();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBootstrapEnabled
public LegacyContextRefresher legacyContextRefresher(ConfigurableApplicationContext context, RefreshScope scope,
			RefreshProperties properties) {
	return new LegacyContextRefresher(context, scope, properties);
}
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
	return new RefreshEventListener(contextRefresher);
}

从spring.factroies自动装配规范文件中我们能看到RefreshAutoConfiguration类,从类名也能获取到很多信息。再从其中的@Bean中可以看到RefreshScope 、LegacyContextRefresher 、RefreshEventListener三个类。那么下面从笔者的解释和源码深入理解这三个类~!

  • RefreshScope:扩展@Scope注解,并且CRUD名字为refresh的@Scope注解类,之前介绍的@RefreshScope注解中存在value为refresh的@Scope注解。
  • LegacyContextRefresher:用来刷新Environment。
  • RefreshEventListener:用来监听ApplicationReadyEvent和RefreshEvent事件。
public class RefreshScope extends GenericScope
		implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered 
// RefreshScope的父类
// 实现了BeanFactoryPostProcessor扩展接口
public class GenericScope
		implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean 
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	this.beanFactory = beanFactory;
    // 把当前Scope注册到BeanFacotry中。
	beanFactory.registerScope(this.name, this);
	setSerializationId(beanFactory);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
	for (String name : registry.getBeanDefinitionNames()) {
		BeanDefinition definition = registry.getBeanDefinition(name);
		if (definition instanceof RootBeanDefinition) {
			RootBeanDefinition root = (RootBeanDefinition) definition;
			if (root.getDecoratedDefinition() != null && root.hasBeanClass()
					&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
				if(getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
                    // 再一次的偷天换日
					root.setBeanClass(LockedScopedProxyFactoryBean.class);
					root.getConstructorArgumentValues().addGenericArgumentValue(this);
					root.setSynthetic(true);
				}
			}
		}
	}
}

这里注意到RefreshScope自动注入的类,这里需要区分@RefreshScope注解和RefreshScope类。它的父类GenericScope实现了BeanFactroyPostProcessor。在postProcessBeanDefinitionRegistry回掉中可以清楚的看到再一次上演了"偷天换日",把ScopedProxyFactoryBean换成了LockedScopedProxyFactoryBean。并且在postProcessBeanFactory回掉中把RefreshScope作为Scope注册到BeanFacotry工厂中。

// SmartApplicationListener子类监听器
public class RefreshEventListener implements SmartApplicationListener 
// 事件回掉。
@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationReadyEvent) {
		handle((ApplicationReadyEvent) event);
	}
    // 一定要注意到RefreshEvent事件,非常非常非常重要!!!
    // 后续我们只需要找到哪里发出的这个事件即可。
	else if (event instanceof RefreshEvent) {
		handle((RefreshEvent) event);
	}
}
public void handle(RefreshEvent event) {
	if (this.ready.get()) { 
		Set<String> keys = this.refresh.refresh();
	}
}
public synchronized Set<String> refresh() {
    // 刷新Environment上下文。而我们知道配置数据是放在Environment中的。
	Set<String> keys = refreshEnvironment();
    // 调用scope的refreshAll,从方法就可以知道,要刷新所有的数据
	this.scope.refreshAll();
	return keys;
}
  • RefreshEventListener作为SmartApplicationListener的子类实现onApplicationEvent方法
  • 监听ApplicationReadyEvent和RefreshEvent事件
  • 调用ContextRefresh类(LegacyContextRefresher)的refresh方法
  • refreshEnvironment方法刷新Environment,返回发生改变的配置数据
  • 调用RefreshScope类的refreshAll方法,把整个RefreshScope中存放的Bean给destroy。
public synchronized Set<String> refreshEnvironment() {
    // 拿到更新前的Environment
	Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
    // 更新Environment。
	updateEnvironment();
    // 对比更新后和更新前,返回发生变化的数据。
	Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
	this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
	return keys;
}
@Override
protected void updateEnvironment() {
	addConfigFilesToEnvironment();
}
ConfigurableApplicationContext addConfigFilesToEnvironment() {
	ConfigurableApplicationContext capture = null;
	try {
		StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());
		…………
		// 创建一个Spring Boot的启动器,目的:为了创建出Spring的上下文(这样整个上下文刷新就可以得到最新的environment)。
		// 这里要注意,刷新Spring上下文的environment是手动放入的,
		// 也即重新刷新Spring上下文的环境变量会加载到手动创建的environment中
		SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF)
		.web(WebApplicationType.NONE).environment(environment);
		builder.application().setListeners(
			Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener()));
		capture = builder.run();
		MutablePropertySources target = getContext().getEnvironment().ge编程客栈tPropertySources();
		String targetName = null;
		// 把再次刷新Spring上下文environment得到的数据赋值到原有的environment环境变量中(这不就完成了配置数据的刷新么)
		for (PropertySource<?> source : environment.getPropertySources()) {
			if (!this.standardSources.contains(name)) {
				if (target.contains(name)) {
					target.replace(name, source);
				}
				else {
					if (targetName != null) {
						target.addAfter(targetName, source);
							// update targetName to preserve ordering
						targetName = name;
					}
					else {
							// targetName was null so we are a编程t the start of the list
						target.addFirst(source);
						targetName = name;
					}
				}
			}
		}
	}
	// finally中把临时创建出的Application上下文给关闭。
	finally {
		ConfigurableApplicationContext closeable = capture;
		while (closeable != null) {
			closeable.close();
			if (closeable.getParent() instanceof ConfigurableApplicationContext) {
				closeable = (ConfigurableApplicationContext) closeable.getParent();
			}
			else {
				break;
			}
		}
	}
	return capture;
}

这里就是重点所在。一言以蔽之:手动创建Environment对象,然后重新走一遍上下文刷新,这样可以得到最新的配置文件,然后把手动创建Environment对象赋值给当前旧的上下文,这样就完成了动态刷新配置。详细流程如下:

  • 创建出Environment对象。
  • 创建SpringApplicationBuilder对象,也即Spring Boot的启动器(手动传入创建出的Environment对象,这样刷新上下文的时候使用的是这里创建的Environment对象)
  • 调用SpringApplication的run方法,刷新Spring Boot和Spring上下文(刷新完成后返回Spring上下文)。
  • 把刷新Spring Boot和Spring上下文得到的Environment对象的属性拷贝到当前Spring上下文中
  • close掉刷新完成后返回Spring上下文的。
public void ref开发者_Python培训reshAll() {
	super.destroy();
	this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void destroy() {
	List<Throwable> errors = new ArrayList<Throwable>();
    // 清空缓存
	Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
    // 调用摧毁的回掉函数
	for (BeanLifecycleWrapper wrapper : wrappers) {
		try {
			Lock pythonlock = this.locks.get(wrapper.getName()).writeandroidLock();
			lock.lock();
			try {
				wrapper.destroy();
			}
			finally {
				lock.unlock();
			}
		}
		catch (RuntimeException e) {
			errors.add(e);
		}
	}
}

这里就是把RefreshScope中所有的缓存的给clear,并且回掉Bean的destroy方法。这里我必须再次强调RefreshScope的作用,就是CRUD 类上标有value为"refresh"的@Scope注解(@RefreshScope不就是么,所以我们的ConsumerController这个Bean就是交给RefreshScope管理,可能到这里笔者有点懵逼,为什么ConsumerController交给RefreshScope管理?他不是Spring的Bean么,不是要进入三级缓存中么???那么下面就是为了解答这个问题。)

我们看到getBean的doGetBean方法创建Bean的流程中。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
    // 创建singleton的bean
	if (mbd.isSingleton()) {
        …………
	}
    // 创建Prototype的bean
	else if (mbd.isPrototype()) {
		…………
	}
    // 创建其他Scope作用域的bean
	else {
		String scopeName = mbd.getScope();
		if (!StringUtils.hasLength(scopeName)) {
			throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
		}
        // 根据name拿到对应的Scope。
		Scope scope = this.scopes.get(scopeName);
		if (scope == null) {
			throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
		}
		try {
            // 调用scope的get方法,所以对应的scope可以缓存。
			Object scopedInstance = scope.get(beanName, () -> {
				beforePrototypeCreation(beanName);
				try {
					return createBean(beanName, mbd, args);
				}
				finally {
					afterPrototypeCreation(beanName);
				}
			});
			beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
		}
		catch (IllegalStateException ex) {
			throw new ScopeNotActiveException(beanName, scopeName, ex);
		}
	}
}

在else代码块中会去处理不同Scope作用域的Bean,而在RefreshScope的父类GenericScope中的postProcessBeanFactory回掉方法中(前面有介绍)会把RefreshScope作为一个Scope注册到BeanFactory中(所以上文的refreshAll方法把RefreshScope的缓存全部clear掉了,下次就会去createBean,就会重新走一遍Spring创建Bean的过程,而环境变量已经更改了,@Value注解的注入就会注入到新的环境变量中的配置数据)。

这里把RefreshScope的缓存全部clear掉了,那么总要有一个地方每次都来拿一遍数据(这样缓存在就拿缓存的,缓存不在(缓存不在就代表被clear了,而clear掉了代表有地方发生了RefreshEvent事件,执行了refreshAll方法和refreshEnvironment,缓存被清除了,环境变量被更改了)就重新createBean,拿到最新的环境变量)

此时,我们是不是忘了,我们的ConsumerController被代理了呢?上文介绍RefreshScope的父类GenericScope中postProcessBeanDefinitionRegistry方法注册了LockedScopedProxyFactoryBean。

public static class LockedScopedProxyFactoryBean<S extends GenericScope> extends ScopedProxyFactoryBean
			implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
	    // 反射的Method
        Method method = invocation.getMethod();
	    …………
	    try {
		    if (proxy instanceof Advised) {
			    Advised advised = (Advised) proxy;
			    ReflectionUtils.makeAccessible(method);
			    // advised.getTargetSource().getTarget()方法会去getBean
			    return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(),
				invocation.getArguments());
		    }
		    return invocation.proceed();
	    }
    }
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
	@Override
	public Object getTarget() throws Exception {
		// 看到熟悉的getBean方法了。
		return getBeanFactory().getBean(getTargetBeanName());
	}
}

LockedScopedProxyFactoryBean实现了MethodIntercepythonptor接口,所以只要调用ConsumerController类中的方法就会走到LockedScopedProxyFactoryBean的invoke方法。恰好在invoke方法中会去调用BeanFactory的getBean方法。而getBean再到doGetBean,再到上面介绍的else代码块中,就走到RefreshScope类中的get方法了。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    // 拿缓存,如果缓存中没有就创建
	BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
	this.locks.putIfAbsent(name, new ReentrantReadwriteLock());
	try {
        // 拿缓存,如果缓存中没有就回掉createBean方法。
		return value.getBean();
	}
	catch (RuntimeException e) {
		this.errors.put(name, e);
		throw e;
	}
}

看到get方法其实就恍然大悟了,因为在LockedScopedProxyFactoryBean的invoke方法中会调用getBean,getBean调用doGetBean中会调用scope.get方法,而在get方法中会去拿缓存,如果没有缓存就会创建一个新的,而新的就会去执行createBean方法创建一个新的Bean出来。恰好在这之前缓存已经被清除了,环境变量更新了。最终createBean方法创建的时候@Value注入的就是最新的环境变量中的配置数据。

所以,RefreshScope类+@RefreshScope注解控制了Bean的创建,RefreshEvent事件控制了缓存的clear和环境变量的更新。但是我们似乎还没有闭环RefreshEvent事件在哪里发出的。

public class NacosContextRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {
	private void registerNacosListener(final String groupKey, final String dataKey) {
		// 一个group、dataid对应一组事件。
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		// 创建一个Nacos事件监听器
		Listener listener = listenerMap.computeIfAbsent(key,
			lst -> new AbstractSharedListener() {
				@Override
				public void innerReceive(String dataId, String group,
					String configInfo) {
					// 这里发送了RefreshEvent事件。
					applicationContext.publishEvent(
						new RefreshEvent(this, null, "Refresh Nacos config"));
				}
			});
		try {
			// 添加Nacos的内部事件
			configService.addListener(dataKey, groupKey, listener);
		}
	}
}

对于Nacos的配置中心代码不过细讲,我们能够知道RefreshEvent事件是从Nacos内部发出的即可

到此这篇关于SpringCloud @RefreshScope注解源码层面深入分析的文章就介绍到这了,更多相关SpringCloud @RefreshScope内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜