【Spring源码分析】Spring Scope功能中的动态代理 - Scoped Proxy ...

打印 上一主题 下一主题

主题 910|帖子 910|积分 2730

本文基于Springboot 3.3.2及Springcloud 2023.0.1版本编写。
Spring Scoped Proxy是什么

在使用Spring cloud配置中心动态配置更新功能时,笔者发现在给一个类加上@RefreshScope注解后,此中@Value注入的字段会被自动更新。起初笔者以为Spring在收到配置更新事件后会自动设置该bean的字段值,但测试后发现配置更新是通过重修整个bean的方式来实现的。实行代码如下:
  1. @RestController
  2. public class TestController {
  3.     @Autowired
  4.     private RefreshClazz refreshClazz;
  5.     @GetMapping("/test/url")
  6.     public String getCount() {
  7.         return "count=" + refreshClazz.getCount();
  8.     }
  9.     @Component
  10.     @RefreshScope
  11.     public static class RefreshClazz {
  12.         @Value("${example.config}")
  13.         private String configStr;
  14.         private int count = 0;
  15.         @PostConstruct
  16.         public void postConstruct() {
  17.             System.out.println("POST_CONSTRUCT");
  18.         }
  19.         @PreDestroy
  20.         public void preDestroy() {
  21.             System.out.println("PRE_DESTROY");
  22.         }
  23.         public int getCount() {
  24.             return count++;
  25.         }
  26.     }
  27. }
复制代码
代码中,RefreshClazz 是一个被标志了@RefreshScope的 Bean,通过@Autowired的方式注入到 Controller 中。运行上面的代码,会发现当配置更新后,RefreshClazz 的内部字段 count 被重置到了 0,同时也会输出 PRE_DESTROY 和 POST_CONSTRUCT,说明旧的 Bean 被删除,新的 Bean 被创建了。
那么问题来了,RefreshClazz是被静态注入到 controller 中的,如何做到自动刷新的呢?原理便是注入的是动态代理对象。Spring 在实现诸多功能(如@Lazy、@Transactional、@Cacheable)时都用到了动态代理,Scope 也是此中一个。Scope 功能用到的动态代理被称为 Scoped Proxy。
如何使用 Scoped Proxy

@RefreshScope界说代码:
  1. @Target({ ElementType.TYPE, ElementType.METHOD })
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Scope("refresh")
  4. @Documented
  5. public @interface RefreshScope {
  6.         @AliasFor(annotation = Scope.class)
  7.         ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
  8. }
复制代码
根据@RefreshScope的界说我们可以发现,它等同于@Scope(value = "refresh", proxyMode = ScopedProxyMode.TARGET_CLASS)。其关键在于proxyMode = ScopedProxyMode.TARGET_CLASS。这个参数一共有四个取值:

  • DEFAULT:默认值,等同于NO;
  • NO:不创建scoped proxy,对于非单例Scope的bean来说此模式通常没用;
  • INTERFACES:使用JDK动态代理创建一个基于接口实现的动态代理对象;
  • TARGET_CLASS:使用CGLIB创建一个基于继承的动态代理对象。
    可见,@RefreshScope 默认使用了基于继承实现的动态代理对象。这样做有几点好处:
  • 在使用方注入时既可使用接口注入,也可以使用类型注入。假如 proxyMode = ScopedProxyMode.INTERFACES,创建出的 scoped proxy 类型并非原Bean的类型,而只是实现了它所实现的接口。由于 @Autowired 真正要注入的是 scoped proxy,假如变量界说为 Bean 的类型,Spring 会报 No qualifying bean of type '...' available 错误;
  • JDK实现的动态代理在性能和内存开销上稍大于CGLIB动态代理。
Scoped Proxy 是如何被创建的

Spring Bean 注册

在Spring容器中注册 scoped proxy 的逻辑来自 ScopedProxyUtils#createScopedProxy 方法,该方法的代码如下:
  1. public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
  2.                 BeanDefinitionRegistry registry, boolean proxyTargetClass) {
  3.         // 获取原beanName和bean定义
  4.         String originalBeanName = definition.getBeanName();
  5.         BeanDefinition targetDefinition = definition.getBeanDefinition();
  6.         // 生成被代理bean的beanName
  7.         String targetBeanName = getTargetBeanName(originalBeanName);
  8.         // 创建动态代理Bean的BeanDefinition
  9.         RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
  10.         proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
  11.         proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
  12.         proxyDefinition.setSource(definition.getSource());
  13.         proxyDefinition.setRole(targetDefinition.getRole());
  14.         // 将被代理bean的beanName传给动态代理Bean
  15.         proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
  16.         if (proxyTargetClass) {
  17.                 targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
  18.                 // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
  19.         }
  20.         else {
  21.                 proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
  22.         }
  23.         // 将原bean的属性复制到动态代理bean定义中.
  24.         proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
  25.         proxyDefinition.setPrimary(targetDefinition.isPrimary());
  26.         if (targetDefinition instanceof AbstractBeanDefinition abd) {
  27.                 proxyDefinition.copyQualifiersFrom(abd);
  28.         }
  29.         // 将底层bean隐藏起来,不参与注入.
  30.         targetDefinition.setAutowireCandidate(false);
  31.         targetDefinition.setPrimary(false);
  32.         // 将底层bean的beanName设置为targetBeanName,注册到容器中.
  33.         registry.registerBeanDefinition(targetBeanName, targetDefinition);
  34.         // 返回刚生成的动态代理bean的BeanDefinition,beanName设置为原bean的名字.
  35.         return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
  36. }
复制代码
在进入这个方法之前,这个被@Scope修饰的 Bean 和其他普通单例 Bean 并没有区别。但此方法对它进行了一通魔改,最后将原 Bean 的界说改了个名字藏在了 Spring 容器内部,而袒露出了一个新生成的 scoped proxy bean。由于新的 bean 名字和原 bean 名一样,并且大概是 Primary Bean,因此在 @Autowired 注入时默认就注入了这个动态代理 bean。
原 Bean 的名字被改成了什么呢?可以参考 getTargetBeanName() 方法:
  1. private static final String TARGET_NAME_PREFIX = "scopedTarget.";
  2. public static String getTargetBeanName(String originalBeanName) {
  3.         return TARGET_NAME_PREFIX + originalBeanName;
  4. }
复制代码
因此,底层 Bean 的 beanName 为 scopedTarget.。
动态代理对象生成

在 ScopedProxyUtils#createScopedProxy 方法的代码中,我们注意到新生成的动态代理 Bean 类被设置为了 ScopedProxyFactoryBean.class。这是一个 FactoryBean,负责具体生成动态代理对象。代码如下:
  1. public class ScopedProxyFactoryBean extends ProxyConfig
  2.         implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {
  3.   /** 从Spring容器中获取底层对象的TargetSource. */
  4.   private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
  5.   /** 底层bean的beanName. */
  6.   @Nullable
  7.   private String targetBeanName;
  8.   /** 缓存的单例 Scoped proxy. */
  9.   @Nullable
  10.   private Object proxy;
  11.   /** 构造方法. */
  12.   public ScopedProxyFactoryBean() {
  13.         setProxyTargetClass(true);
  14.   }
  15.   /** 设置底层bean的beanName. */
  16.   public void setTargetBeanName(String targetBeanName) {
  17.         this.targetBeanName = targetBeanName;
  18.         this.scopedTargetSource.setTargetBeanName(targetBeanName);
  19.   }
  20.   /** 创建动态代理对象的主方法. */
  21.   @Override
  22.   public void setBeanFactory(BeanFactory beanFactory) {
  23.         if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
  24.                 throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
  25.         }
  26.     // 为targetSource设置使用的beanFactory
  27.         this.scopedTargetSource.setBeanFactory(beanFactory);
  28.     // 通过ProxyFactory创建动态代理对象
  29.         ProxyFactory pf = new ProxyFactory();
  30.         pf.copyFrom(this);
  31.     // 使用配置好的targetSource
  32.         pf.setTargetSource(this.scopedTargetSource);
  33.         Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
  34.         Class<?> beanType = beanFactory.getType(this.targetBeanName);
  35.         if (beanType == null) {
  36.                 throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
  37.                                 "': Target type could not be determined at the time of proxy creation.");
  38.         }
  39.         if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
  40.                 pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
  41.         }
  42.         // Add an introduction that implements only the methods on ScopedObject.
  43.         ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
  44.         pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
  45.         // Add the AopInfrastructureBean marker to indicate that the scoped proxy
  46.         // itself is not subject to auto-proxying! Only its target bean is.
  47.         pf.addInterface(AopInfrastructureBean.class);
  48.    
  49.     // 生成并缓存动态代理对象
  50.         this.proxy = pf.getProxy(cbf.getBeanClassLoader());
  51.   }
  52.   /** 工厂类的获取生成对象方法,获取生成的动态代理对象. */
  53.   @Override
  54.   @Nullable
  55.   public Object getObject() {
  56.         if (this.proxy == null) {
  57.                 throw new FactoryBeanNotInitializedException();
  58.         }
  59.         return this.proxy;
  60.   }
  61. }
复制代码
这个类使用了 ProxyFactory 创建动态代理对象,它生成的动态代理对象通过接口 TargetSource 来获取代理的底层对象。上面的 ScopedProxyFactoryBean 使用了 SimpleBeanTargetSource,它的代码如下:
  1. public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
  2.   @Override
  3.   public Object getTarget() throws Exception {
  4.         return getBeanFactory().getBean(getTargetBeanName());
  5.   }
  6. }
复制代码
逻辑很清晰,通过 beanFactory 来获取名为 targetBeanName 的bean对象作为被代理的对象。而这个 targetBeanName 在Spring容器中注册 scoped proxy 的时候就被生成了,设置的逻辑是:
  1. proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
复制代码
Scoped Proxy 整体工作逻辑

看了前面的代码,我们可以总结出 scoped proxy 的工作逻辑:

  • 被@Autowired注入的是一个 scoped proxy,它的 BeanDefinition 的 scope 其实是 singleton;
  • 调用 bean 方法时,scoped proxy 在 Spring 容器中获取名为 scopedTarget. 的被代理 bean,此 bean 的 scope 是 refresh;
  • scoped proxy 调用被代理 bean 的对应方法。
Scoped Bean 的生命周期

八股文里,Spring 有五种 Scope(singleton、prototype、request、session、globalSession)(这其实是过时的,最新文档里是六种:singleton、prototype、request、session、application、websocket)。那本文里的 refresh scope 是什么呢?
其实,只需要注入一个实现了 Scope 接口的 Bean,用户便可添加一个自界说的 scope。本文中的 refresh scope 就是 spring-cloud-context 中界说的。Spring 容器在获取任何不是 singleton 或 prototype 的 bean 时,会先找出该 scope 名所对应的 Scope 对象,再使用 Scope#get 从该对象中获取 bean,代码参考 AbstractBeanFactory#doGetBean。而 scoped bean 的生命周期便是由具体的 scope 类管理了(实现案例可以参考 GenericScope)。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

慢吞云雾缓吐愁

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表