当前位置:   article > 正文

Spring 配置 @Scope(value = “prototype“,proxyMode = ScopedProxyMode.TARGET_CLASS) 执行过程分析_@scope(value = "prototype")

@scope(value = "prototype")

示例代码

现在想要往 ScopeProxyBeanDemo 类注入 ScopeProxyBean 类,并且要求注入的 ScopeProxyBean 是个 prototype 类型,ScopeProxyBean 类的方法 code() 中打印 this 对象的 hashCode(),验证是否是不一样的对象,代码如下:

在这里插入图片描述
在这里插入图片描述

然后调用 10 次看下打印的 10 次 hashCode(),发现是一样的,结果如下:

在这里插入图片描述
在这里插入图片描述

发现明明设置了 prototype 多例模式,注入的 ScopeProxyBean 竟然是同一个对象,导致这个问题的原因是,通过 @Autowired 注入其实只调用了一次 getBean() 赋值,所以这个值已经固定了,那么要解决这个问题,Spring 提供几个方法:

  • 自定义 TargetSource 上一篇文章已经解析过怎么自定义 TargetSource 来解决这个问题,这里不多概述。
  • 直接在 @Scope 注解加上proxyMode = ScopedProxyMode.TARGET_CLASS(其实底层本质也是自定义 TargetSource),如下所示:
    在这里插入图片描述
    然后测试结果如下:10 次打印的 hashCode 都是不一样的
    在这里插入图片描述

那么现在就重点来分析为什么这样可以解决上面出现的问题。

可以分为两大步骤:

  • 解析 @Scope 注解
  • 创建 Cglib 代理对象

 

解析 @Scope 注解

ConfigurationClassPostProcessor 类的解析开始,源码如下:

在这里插入图片描述在这里插入图片描述在这里插入图片描述

进入 resolveScopeMetadata() 方法,源码如下:会在这里解析出 @Scope 配置的 proxyMode,然后封装到元数据 metadata

在这里插入图片描述

解析完了 @Scope 之后就要开始去为这个 beanClass 创建一份代理类的 BeanDefinition 定义了,回到上一层代码调用处:

在这里插入图片描述

进入 applyScopedProxyMode() 方法,注意哦这里只是在生成一份代理类的 ProxyBeanDefinition 定义,源码如下:

在这里插入图片描述

进入 createScopedProxy() 方法,源码如下:

在这里插入图片描述

总结一下 createScopedProxy() 方法把原始的 BeanDefinition 的 autowireCandidate 属性修改成了 false,因为改了这个值,所以在 @Autowired 属性注入的时候,就不会考虑到这个原生的 BeanDefinition 定义了

这里自己给造了一份新的这个 bean 的 BeanDefinition,可以理解为这个 bean 的代理 BeanDefinition,此时容器中已经存在两份 bean 的 BeanDefinition,只是原生的 BeanDefinition 已经不能参团了。

然后 BeanDefinition 中的 beanClass 被修改成了 ScopedProxyFactoryBean,这个类起到了关键性作用,既然图纸(ProxyBeanDefinition)造好了,是不是要去根据这份图纸造代理对象了。

那就看到这个类 ScopedProxyFactoryBean 就可以了,进入源码如下:可以看见是非常敏感的类 FactoryBean、和 BeanFactoryAware 。为什么敏感?FactoryBean 规范是通过里面提供的 getObject() 方法造对象的,BeanFactoryAware 规范在 Spring 实例化过程中会回调 BeanFactoryAware 类的 setBeanFactory() API。
所以重点关注这两个类的这两个方法。这样分析源码就非常顺畅,不会一头雾水。

在这里插入图片描述

所以直接看 getObject()、setBeanFactory() 这两个API,源码如下:可以看到 getObject() 方法直接返回了 proxy 对象,那么现在就只要去追踪这个 proxy 在哪里生成写入值就可以了。

在这里插入图片描述

刚好就是在 setBeanFactory() 方法中,源码如下:所以重点就看 setBeanFactory() 里面的逻辑了

在这里插入图片描述

这里重点看下 TargetSource 是谁?是 scopedTargetSource 变量,因为 TargetSource 有一个非常重要的方法: getObject() 会返回实例 bean,上篇文章中我们自己自定义的 TargetSource,就是模仿这里写的。源码如下:

在这里插入图片描述

同样的 getTarget() 方法也是直接去调用 getBean() 方法实例化 bean,但是需要注意,这里的 BeanFactory 不是深拷贝的,而是原原本本的 BeanFactory,如果这里还启动了切面功能,配置了注解 @EnableAspectJAutoProxy,@Aspect 该怎么样就是怎么样,不会影响这个功能。自定义的 TargetSource 就不一样了(会踢除 @Aspect 切面功能)

注意到没这里已经创建好了代理对象,所以 @Aspect 你觉得还回去创建一次代理对象么?答案是不会的,源码如下:

在这里插入图片描述

在后置处理器中有一个拦截操作,拦截如果实现了 AopInfrastructureBean 接口的类,源码如下:

在这里插入图片描述

因为在设置了 @Scope proxyMode=Target_Class 时,是采用的 BeanFactory 方式去获取实例的,刚好 ScopeProxyBean 类的外部 BeanFactory 就是 ScopedProxyFactoryBean 类,这个类刚好实现了 AopInfrastructureBean 接口,所以上面的拦截判断条件成立:

在这里插入图片描述

@Aspect 创建代理对象的 if 拦截,如下所示:所以在这一次 Spring 启动过程中不会再去创建一个新的代理对象

在这里插入图片描述

但是在 ScopedProxyFactoryBean 类调用 getObject() 方法的时候,会触发一次 getBean() 过程,这个时候会触发 @Aspect 生成代理对象。

在这里插入图片描述

okay,这里扯多了,在回到上面的 setBeanFactory() 方法中,源码如下:

在这里插入图片描述

这里注意两点:

1、scopedTargetSource 是直接内部 new 了一个对象

在这里插入图片描述在这里插入图片描述

2、生成的代理对象直接赋值到 proxy 变量,getObject() 方法就是直接返回这个 proxy 对象的,源码如下:

在这里插入图片描述

至此整个解析 @Scope 和 创建 bean 代理过程已经完成了,接下来就是调用方法,然后触发 invoke() 方法,源码如下:

在这里插入图片描述

getTargetSource() 方法获取到的就是上面直接在内部 new 出来的 scopedTargetSource 对象,然后调用 getTarget() 方法就会触发 getBean() 流程返回实例 bean,因为配置的 prototype 模型,所以每次都会返回一个新的 bean,至此
在上面调用 10 次 for 循环就会打印出 10个不同的 hashCode 值。

 

总结一下:

配置 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 的效果总体概括就是通过 Cglib 代理方式创建 bean

配置 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 在 BeanDefinition 中的 beanClass 被偷梁换柱了,表面看起来注册的 beanClass 是 ScopeProxyBean,实则已经被换成了 ScopedProxyFactoryBean,并且该类还是一个 BeanFactory 类,那么 ScopeProxyBean 的实例就有该 BeanFactory 生产和管控了

在这里插入图片描述

此时容器中是存在两份该类的 BeanDefinition,一份是原始的 BeanDefinition、另一份是代理 BeanDefinition,原始 BeanDefinition 中的属性 autowireCandidate 被设置成 了 false ,不能参与注入

在这里插入图片描述

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号