前言
源码部分涉及的版本
- spring-boot-version:2.6.7
- spring-cloud-version:2021.0.1.0
先从 @RefreshScope 注解源码观察,如下:
package org.springframework.cloud.context.config.annotation; // 可标注在类以及方法上,方法上一般与 @Bean 组合 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Scope("refresh") // 动态刷新特有 scope 标识 @Documented public @interface RefreshScope { /** * 默认代理:目标类 CGLIB * @see Scope#proxyMode() * @return proxy mode */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; }RefreshScope
结合 Spring 生命周期来看,每个 Bean 最终的实例化、初始化都需要有 Bean 定义信息存在,才能被 Spring 所扫描后注入,它就是 BeanDefinition(简写 BD)
,所以在这里先要清楚在处理标注了 @RefreshScope 注解的类、方法是如何处理 BD 的,@RefreshScope 标注的 Bean,在 Spring Cloud 中它使用了类似热部署的方式,动态刷新了属性值
Spring-cloud 是以 spring-boot 基础组件进行实现的,一般都是以注解方式进行开发,之前有文章分析过注解扫描的核心类就在于 ConfigurationClassPostProcessor 中
结合 @RefreshScope 所在的 package 包名,它来自 spring-cloud-context 模块,在该模块下会自动装配下两个核心类:RefreshScope、RefreshEventListener
ConfigurationClassPostProcessor 类准备工作处理
先观察 ConfigurationClassPostProcessor、RefreshScope 类图:
从以上两张图来看,ConfigurationClassPostProcessor 实现了 BFPP、BDRPP、PriorityOrdered,RefreshScope 也实现了 BFPP、BDRPP 但它只实现了 Ordered;在以前文章讲解了 Refresh 中 invokeBeanFactoryPostProcessors
核心方法时,已经可以知道 ConfigurationClassPostProcessor 会优先加载,然后再加载 RefreshScope
PriorityOrdered > Ordered > NonOrdered
优先加载 ConfigurationClassPostProcessor 类时,扫描注解 ClassPathBeanDefinitionScanner#doScan 方法中会在内部调用 AnnotationConfigUtils#applyScopedProxyMode 方法,以下是它的源码:
static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { // 获取 @Scope 注解 proxyMode 属性值 ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); if (scopedProxyMode.equals(ScopedProxyMode.NO)) { return definition; } // 属性值=目标类 情况下,调用 ScopedProxyCreator#createScopedProxy boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); }
当前这个是当 @RefreshScope 标注在类上的情况下,还有一种标注在 @Bean 方法的场景,核心处理在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod 方法,以下是它的部分源码:
ScopedProxyMode proxyMode = ScopedProxyMode.NO; AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class); if (attributes != null) { beanDef.setScope(attributes.getString("value")); proxyMode = attributes.getEnum("proxyMode"); // 获取 @Scope 注解的 proxyMode 属性值,DEFAULT 就是不代理 if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = ScopedProxyMode.NO; } } // Replace the original bean definition with the target one, if necessary BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { // 这里发现它和处理类时调用了同样的方法 BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS); beanDefToRegister = new ConfigurationClassBeanDefinition( (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName); }
所以在这里要观察它是如何提前处理这些准备工作的
// ScopedProxyCreator#createScopedProxy->ScopedProxyUtils#createScopedProxy public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); // "scopedTarget." + originalBeanName:拼接目标类的名称 String targetBeanName = getTargetBeanName(originalBeanName); // 此时 proxyDefinition scope 属性就是 "" 值了,在 AbstractBeanDefinition 无参构造可以看出 RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(targetDefinition.getRole()); // proxyDefinition 动态追加一个属性 targetBeanName proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName); if (proxyTargetClass) { // 设置属性:preserveTargetClass targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); } else { // 设置属性:proxyTargetClass proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE); } proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition)targetDefinition); } targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); // 注册一个新的 BD,它是目标类 registry.registerBeanDefinition(targetBeanName, targetDefinition); // 调整传递过来的 BD,它是原始类:为它修改了原有的 BeanClass 以及新增了一个属性 targetBeanName return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); }
从以上来说确实会有点混乱,下面进行举例:
原始 Bean:AppConfig,经过 createScopedProxy 方法处理以后
1、原始 Bean:AppConfig,会追加一个 targetBeanName 属性,它的属性值就是
scopedTarget.appConfig
,同时修改原始 Bean 的 beanClass 为 ScopedProxyFactoryBean 后返回2、第一点是原始 Bean 处理过后的信息会返回,然后以上源码会新增一个 BD,它把原始 Bean 所有信息都赋值了过来,但它的 beanName 不再是以前的 appConfig 了,而是变成了 scopedTarget.appConfig
好,到这里 ConfigurationClassPostProcessor 处理 @RefreshScope 注解的工作已经完成了,关于 ConfigurationClassPostProceessor 更多功能的源码介绍,可以阅读该文章:Spring 核心类 ConfigurationClassPostProcessor 流程讲解及源码全面分析
RefreshScope 类准备工作
在 Spring 中,scope 只会存在这几种作用域,如下图:
那么它是如何去处理 scope=refresh
这种作用域的呢?
从以上的 RefreshScope 类图可以看出,它继承了 GenericScope 类,而它的父类实现了 BFPP、BDRPP 接口,它会和 ConfigurationClassPostProcessor 同时处理,只不过在它的后面,所以在这里就需要看 GenericScope 类是如何处理这两个方法的呢?
BDRPP#postProcessBeanDefinitionRegistry 方法会优先加载,源码如下:
public RefreshScope() { super.setName("refresh"); } // GenericScope.java 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; // root.getBeanClass() == ScopedProxyFactoryBean.class 条件就说明了是原始 BD if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { // getName()=refresh,BD 的 DecoratedDefinition 属性是目标 BD,那么此时肯定是满足条件的 if (getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) { // 把 BeanClass 重新调整为 LockedScopedProxyFactoryBean root.setBeanClass(LockedScopedProxyFactoryBean.class); root.getConstructorArgumentValues().addGenericArgumentValue(this); root.setSynthetic(true); } } } } }
BFPP#postProcessBeanFactory 方法会后面才加载,源码如下:
// GenericScope.java public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; // 当前的 this.name 是 Generic,但它的子类构造方法将它的 name 属性修改为了 refresh // registerScope:代表 AbstractBeanFactory#scopes 会新增一个元素:key=refresh、value=RefreshScope.class beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }
这些 BD 信息准备工作作好了,后续就是这些 Bean 进行加载了.