最近在研究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;
}
}
能够正常使用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 doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set 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 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底子好的读者很容易看明白。下面是改变的流程图。
那么,说了这么多的意义在哪里呢?我们的主题不是动态刷新么,怎么连注册中心的影子都没见到?年轻人,不要着急,文章开头就说了本篇文章跨度很大~
此时,我们需要从Spring Cloud规范包入手,相信各位读者知道,Spring-Cloud-Commons和Spring-Cloud-Context 这两个Spring Cloud规范包。从他们的spring.factories自动装配包可以得知以下信息。
@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三个类。那么下面从笔者的解释和源码深入理解这三个类~!
public class RefreshScope extends GenericScope
implements ApplicationContextAware, ApplicationListener, 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 keys = this.refresh.refresh();
}
}
public synchronized Set refresh() {
// 刷新Environment上下文。而我们知道配置数据是放在Environment中的。
Set keys = refreshEnvironment();
// 调用scope的refreshAll,从方法就可以知道,要刷新所有的数据
this.scope.refreshAll();
return keys;
}
public synchronized Set refreshEnvironment() {
// 拿到更新前的Environment
Map before = extract(this.context.getEnvironment().getPropertySources());
// 更新Environment。
updateEnvironment();
// 对比更新后和更新前,返回发生变化的数据。
Set 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().getPropertySources();
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 at 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对象赋值给当前旧的上下文,这样就完成了动态刷新配置。详细流程如下:
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Override
public void destroy() {
List errors = new ArrayList();
// 清空缓存
Collection 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);
}
}
}
这里就是把RefreshScope中所有的缓存的给clear,并且回掉Bean的destroy方法。这里我必须再次强调RefreshScope的作用,就是CRUD 类上标有value为"refresh"的@Scope注解(@RefreshScope不就是么,所以我们的ConsumerController这个Bean就是交给RefreshScope管理,可能到这里笔者有点懵逼,为什么ConsumerController交给RefreshScope管理?他不是Spring的Bean么,不是要进入三级缓存中么???那么下面就是为了解答这个问题。)
我们看到getBean的doGetBean方法创建Bean的流程中。
protected T doGetBean(String name, @Nullable Class 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 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实现了MethodInterceptor接口,所以只要调用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, 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内部发出的即可
确实跨度非常大,设计到Spring全家桶的各个部分的源码,可能对于一些读者来说欠缺一小部分的前置知识源码就没办法继续读下去了,但是笔者也没办法把所有细节和所有需要的前置知识都写在此篇文章中~ 笔者能做到的是把整个源码闭环,也欢迎大家积极评论~!
最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!