文章目录

  • Bug复现
  • 结论
  • @PostConstruct的在Bean的生命周期的哪一步
  • 一般代理类的生成时机在生命周期的哪一步
  • 解决办法
    • 两个思路
    • 1.不生成代理类
    • 2.在生成代理类之后再进行数据的初始化
  • 解决方法的原理
  • 早期代理Bean是什么时候生成的
  • 循环依赖
  • 结语

喜欢省流太长不看的同学们
东老师vme50解决bug
bug是由于postconstuct注解使用时代理类未生效的问题
由于东老师的胡搅蛮缠,讲了讲bug的原因
没有深入讲源码,因为东老师给的钱不够
顺便讲了讲解决思路及原理
解决方案1.自己注入自己 2.使用编程式事务
动态代理及循环依赖的大体逻辑

上周四需要对一个老功能进行迭代,功能开发完毕后,有数个表增加了些字段,需要对相关表模型老数据进行数据清洗。
负责这个功能的好兄弟东老师在预发环境遇到了一个问题,抛给我看看是咋回事,demo代码如下
这个场景很常见

需求改了之前的逻辑,历史数据需要清洗
如果线上和预发环境是一个库(我们就是),为了方便大多数会在预发环境对线上的数据进行清洗

东老师:浪啊帮我看看这段代码有问题吗
东老师是我的hxd,所以对于这样的诚恳请求
我很有礼貌:滚 没时间
东东老师发出了一段神秘的代码:kfcVYou50
我:不好意思,东老师,刚才怪我我声音太大,咱们互帮互助,我义不容辞,哪个分支我看下,对了,v50到我微信
接着check out,pull
我心想凭借这么多年写bug的经验 ,还有什么风浪没见过
果然这bug只是个别致的小东西罢了
只是东老师像个刚被我抛弃的怨妇,非要我深入讲讲
为了这个神秘的代码kfcvme50,我也只好由着东老师的性子来谈谈这段代码的逻辑
我告诉东老师这次我只在旁边蹭蹭不进去,不深入聊源码
东老师还是大怒,我给了钱的
我说你想吃三碗的粉,只给一碗的钱,想讲源码,得加钱
当然了,我也不会告诉东老师,源码太多,我也不太会,只能说没时间

Bug复现

粗略看了下代码,没啥问题呀,d老师莫不是耍我。
先启动下看看,嚯,还真报错了

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testPostConstructServiceImpl': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:415) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) ~[spring-boot-2.2.13.RELEASE.jar:2.2.13.RELEASE]at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.13.RELEASE.jar:2.2.13.RELEASE]at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405) ~[spring-boot-2.2.13.RELEASE.jar:2.2.13.RELEASE]at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.13.RELEASE.jar:2.2.13.RELEASE]at com.zhanghl.first.FirstApplication.main(FirstApplication.java:26) [classes/:na]
Caused by: java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.at org.springframework.aop.framework.AopContext.currentProxy(AopContext.java:69) ~[spring-aop-5.2.12.RELEASE.jar:5.2.12.RELEASE]at com.zhanghl.first.controller.TestPostConstructServiceImpl.addProduct(TestPostConstructServiceImpl.java:33) ~[classes/:na]at com.zhanghl.first.controller.TestPostConstructServiceImpl.init(TestPostConstructServiceImpl.java:59) ~[classes/:na]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]... 16 common frames omittedDisconnected from the target VM, address: '127.0.0.1:60861', transport: 'socket'

报错找不到当前的代理对象

Cannot find current proxy: Set ‘exposeProxy’ property on Advised to ‘true’ to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.

很容易定位到引起这个报错的代码

((TestPostConstructServiceImpl)AopContext.currentProxy()).save();

说明一下这个地方为什么要用AopContext.currentProxy(),大伙熟悉**@Transactional**注解的应该都很清楚,有如下几种情况导致事务注解会失效

1.非public修复的方法
2.同一个类普通方法调用事务方法
3.rollobackFor属性异常类不对
4.异常被catch
5.数据库本身不支持事务等

这个demo代码中符合上面的第二种情形,东老师使用的没毛病。
当然了,也可以使用编程式事务的方式可以代替注解的使用,从而不需要使用AopContext.currentProxy(),也就能避免这种情况的发生,但本文就这种情况进行讨论。


首先我们来注释掉@PostConstruct正常调用下接口看看,返回正常


再看下添加了@PostConstruct之后的报错提示

Cannot find current proxy

结论

回想一下之前看过的Spring源码,背过的八股文,Bean的生命周期,Spring动态代理,Bean的循环依赖。
得出结论:@PostConstruct起作用时,TestPostConstructServiceImpl的代理类并没有生成。

东老师:怎么得出来的这个结论
我:你这属于技术咨询,得加钱
东老师大怒:歪日 你这黑心商人,v你50 你忘了,我举报你
我一看东老师宁为玉碎,不为瓦全的铁骨铮铮模样,决定大发慈悲,拯救下无知的办公室老年人了


@PostConstruct的在Bean的生命周期的哪一步

根据异常信息,我们很容易就可以定位到报错的那行代码。
AbstractAutowireCapableBeanFactory 的initializeBean() Bean的初始化方法,在Bean populateBean() 填充属性之后
initializeBean 的主要作用有如下几点

  1. 如果实现了Aware接口,回调相关Aware接口的逻辑 例如 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware等接口
  2. 执行BeaPostProcessor的前置回调方法
  3. 执行初始化方法
  4. 执行BeanPostProcessor的后置回调方法

其中@PostConstruct执行的地方在第二步applyBeanPostProcessorsBeforeInitialization()前置回调,在将断点打在这里,进去看看堆栈信息,和执行过程。

这里我们快速定位到CommonAnnotationBeanPostProcessor,通过名字我们就知道它的作用了,通用的注解后置处理器
在执行CommonAnnotationBeanPostProcessor的前置回调时,看到了我们遇到的报错信息,没有找到代理类。

一般代理类的生成时机在生命周期的哪一步

一般,注意我们这里说的一般代理类,Spring中代理类的生成时机不止一个地方,后面我们的解决方案也会再讲一下。
一般代理类生成时机是在我们上面说的第四步

执行BeanPostProcessor的后置回调方法

我们去掉@PostConstruct注解再次Debug看下
AnnotationAwareAspectJAutoProxyCreator Spring代理实现核心AbstractAutoProxyCreator的子类
AnnotationAwareAspectJAutoProxyCreator 在后置处理方法中对原有目标类进行代理,生成了代理类。

Spring的动态代理核心逻辑就在这里,很重要,建议自己debug瞅瞅
AbstractAutoProxyCreator 的wrapIfNecessary()方法
AbstractAutoProxyCreator 的wrapIfNecessary()方法
AbstractAutoProxyCreator 的wrapIfNecessary()方法
它实现了 BeanPostProcessor、SmartInstantiationAwareBeanPostProcessor 和 InstantiationAwareBeanPostProcessor 三个接口,那么这个类就是 Spring AOP 的入口,在这里将 Advice 织入我们的 Bean 中,创建代理对象。

这块后置逻辑处理完,可以看到我们收获了当前类的代理类

东老师:你小子不戳哦,那我该怎么解决呢


解决办法

两个思路
1.不生成代理类

这种很好理解,这种思路是不通过切面代理的方式对方法进行事务处理
比如说将事务方法单独抽出一个类
当然更好的方式是使用编程式事务,此处不再赘述
或者在应用程序启动完毕之后,通过调用接口的方式对数据进行初始化

2.在生成代理类之后再进行数据的初始化

东老师:既然上面的方法可以了,你可以告退了
我意犹未尽:东老师,难道你不想做我就是我不一样的花火吗,难道你就想和外面的妖艳贱货一样吗,难道你不想进去看看吗
东老师挣扎了下,想起来自己还v我50不能亏决定继续听我bb。

正如我上面所说一般代理类生成时机是在Bean的初始化之后,
也就是上面说的AbstractAutowireCapableBeanFactory 的initializeBean()第四步,这种场景下我们无法实现。
基于上述的业务场景,Spring作为当前Java生态里最流行的框架,当然也提供了解决方法。
自己注入自己

我们可以看下执行结果


解决方法的原理

带着疑问来看看为什么Bean自己注入自己的方式能生效
我们只需要专注以下两个个问题

1.是否生成了代理类
2.如果是,那么代理类是在什么时候生成的

和上文分析一样,我们还是使用之前的断点,看下@PostConstruct注解被执行的时候上下文,也就是initializeBean()方法中执行BeanPostProcessor接口前置回调方法时候,其中CommonAnnotationBeanPostProcessor的前置回调方法,会处理@PostConstruct注解。
看下CommonAnnotationBeanPostProcessor的类注释

执行初始化的方法

invokeInitMethods(Object target, String beanName)
Target就是我们的当前类,看下图的堆栈信息当前Target是一个普通对象,这个对象包含两个属性
1.productMapper不重要不谈
2.testPostConstructService 重点

这不是我们当前Bean注入自己的属性嘛
可以看到这个属性是个代理类,解答了我们的第一个问题 --‘是否生成了代理类’
这个属性可以看到它本身的另外两个属性是空的,说明这个代理类是个不成熟的Bean(早期Bean,说到这是不是想到了什么)
注意哈 此时Spring中对于TestPostConstructServiceImpl demo类存在两个Bean,一个 当前Bean,一个早期代理Bean


当前Target,也就是TestPostConstructServiceImpl要执行的init方法。


从上述分析可知,在Bean的初始化完成之前,Bean自己注入自己,会产生一个早期代理对象来当作属性。
接下来我们的问题是
早期代理Bean是什么时候生成的
以及
为什么要生成早期代理Bean


早期代理Bean是什么时候生成的

当前场景下,TestPostConstructServiceImpl自己注入自己,注入的这个Bean是个早期代理对象

那么我们只需要看Bean在属性填充的时候,是怎么生成这个代理对象的就可以了。

断点打在AbstractAutowireCapableBeanFactory 的populateBean() 填充属性的方法上。

populateBean()方法内部,逻辑很多,如下所示,InstantiationAwareBeanPostProcessor这个后置处理器就是我们寻找了好久的小宝贝,
这个接口InstantiationAwareBeanPostProcessor在SpringBean的生命周期中占据着核心位置。
在这里插入图片描述AutowiredAnnotationBeanPostProcessor实现了InstantiationAwareBeanPostProcessor,其中
postProcessProperties()中
inject()方法也是我们通常所说的依赖注入的由来
看下类注释
此处将@Autowired 修饰的属性注入到当前Bean

循环依赖

1.对于普通的Bean注入这块逻辑很简单,再去获取需要注入Bean的实例,走一遍创建的逻辑,将需要注入进来的Bean实例化给当前Bean即可
2.对于我们这个场景,你想到了什么,自己注入自己,自己依赖自己,这不就妥妥的循环依赖嘛
来看下第二点,这种循环依赖是怎么解决的
继续跟着inject,往里面探究,是怎么给当前Bean注入自己的。

注意看上图在inject()的执行过程中,又执行到了==getBean()==方法,开始了套娃之旅,注意和左边的堆栈信息对比,那么当前Bean还会执行创建一次吗。
下图,跟着执行可以看到注入的这个Bean getSingleton()又开始执行了
但是从三级缓存里拿到的对象工厂并不为空
但是从三级缓存里拿到的对象工厂并不为空
但是从三级缓存里拿到的对象工厂并不为空
这个是和当前Bean的最大区别,当前Bean第一次创建过程中,这个地方为null。

再来看一下,这个ObjectFactory对象工厂偷偷摸摸的干了啥,一个函数式接口


执行进入getObject()方法

嚯,这不就是懒加载放进去了个早期Bean引用吗
那么 :
1.这个引用是个啥,
2.什么时候放进去的呢

先看看这个引用是什么
画黑板,敲重点,如下方法注释,为了解决循环依赖
这样的逻辑我们应该看到了很多次,遍历所有的BeanPostProcessor(),在Bean生命周期的不同阶段处理不同的回调逻辑,Spring通过这种方式在Bean生命周期的过程中,有着很强的拓展性。
这次需要处理的是SmartInstantiationAwareBeanPostProcessor ==getEarlyBeanReference()方法
老规矩 先看注释
核心大意是 :让Bean提前暴露出去,以便尽早访问指定的bean,通常用于解析循环引用

在SmartInstantiationAwareBeanPostProcessor的实现类AnnotationAwareAspectJAutoProxyCreator
中调用了AbstractAutoProxyCreator 的getEarlyBeanReference();
来跟我念一下
AbstractAutoProxyCreator,wrapIfNecessary()
AbstractAutoProxyCreator,wrapIfNecessary()
== AbstractAutoProxyCreator,wrapIfNecessary()
这不就是我们强调的Spring动态代理的核心逻辑吗
也就是在这里我们获得了当前Bean的Cglib代理类

东老师:哦吼,详细说说这块逻辑
我:滚,再v50
东老师:当我没说

wrapIfNecessary这里的逻辑本文不过多讨论,因为我写不完。

这个引用就是通过Spring的AbstractAutoProxyCreator给当前Bean创建一个动态代理
那么回到第二个问题
这个代理什么时候放进去的呢

东老师听到这,瞬间羞涩:你好坏,什么进去不进去的
我大怒:滚

我们回到当前Bean的创建过程中,注意我说的不是作为属性需要注入的Bean,
而是我们Bean第一次初始化的过程中 AbstractAutowireCapableBeanFactory 的doCreateBean()方法


如上图,在当前Bean的填充属性及初始化之前,我们将这个懒加载的函数放进了三级缓存中。
然后就是我们上面讨论的流程

  1. 填充属性
  2. 依赖注入
  3. 从三级缓存中获取要注入Bean的实例(实际是这个懒加载函数)
  4. 执行懒加载的函数:AbstractAutoProxyCreator创建代理类后
  5. 注入获取到的代理类

最终在当前Bean初始化阶段initializeBean()执行int方法时
可以拿到注入的代理类,从而可以顺利执行

((TestPostConstructServiceImpl)AopContext.currentProxy()).save();

结语

东老师心花怒放:浪啊你真棒
我:滚,送你张图吧 东老师 好好学习,小心脱下长衫,黄袍加身,美团小哥欢迎你

如果你看完了这篇文章,恭喜你又get了一些没什么diao用的知识点

从一个Spring动态代理Bug聊到循环依赖相关推荐

  1. 【Spring AOP】静态代理设计模式、Spring 动态代理开发详解、切入点详解(切入点表达式、切入点函数)

    AOP 编程 静态代理设计模式 1. 为什么需要代理设计模式 2. 代理设计模式 名词解释 代理开发的核心要素 静态代理编码 静态代理存在的问题 Spring 动态代理开发 搭建开发环境 Spring ...

  2. 【Spring源码三千问】Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?

    Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy? 前言 版本约定 正文 例子测试 结论分析 proxyTargetClass 标识的校正 哪些接口不是 Reas ...

  3. 实现一个基于动态代理的 AOP

    实现一个基于动态代理的 AOP Intro 上次看基于动态代理的 AOP 框架实现,立了一个 Flag, 自己写一个简单的 AOP 实现示例,今天过来填坑了 目前的实现是基于 Emit 来做的,后面有 ...

  4. 京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。...

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:cnblogs.com/semi-sub/p/13548479.html 前言 bean生命周期 三级缓存解决循环依赖 总结 ...

  5. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  6. Spring 通过 @Lazy 注解解决构造方法循环依赖问题

    什么是循环依赖? 先定义两个类 Apple.Orange,如下所示: @Component public class Apple{@Autowiredprivate Orange orange; }@ ...

  7. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  8. spring 动态代理_分析动态代理给 Spring 事务埋下的坑

    前言 Spring的声明式事务让我们不在编写获得连接.关闭连接.开启事务.提交事务.回滚事务等代码,通过一个简单的@Transactional注解,就让我们轻松进行事务处理.我们知道Spring事务基 ...

  9. Spring动态代理机制理解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

最新文章

  1. python怎么导入包-python模块之导入包及模块发布
  2. 压缩base 64字符串_ftp下载多个文件,ftp下载多个文件打包成一个压缩包
  3. Effective Java之谨慎地实现Serializable(七十四)
  4. dc持久内存与mysql_Calypso Systems推出测试软件和服务器测试傲腾数据中心级持久内存...
  5. 谷歌核心算法大更新,如何趋利避害对电商网站排名影响?
  6. 支援日本/厄瓜多尔震区 Skype推免费通话
  7. python语法学习_python语法学习笔记
  8. 手机怎么快速把jpg图片中的文字提取出来
  9. Glide4 高效加载图片的配置【转】
  10. LINUX设置终端窗口显示内容的滚动缓冲行数
  11. ong拼音汉字_拼音ong的正确发音
  12. 工作台式计算机配置单,台式电脑配置清单.doc
  13. 云计算是什么?3分钟了解云计算技术
  14. 死神来了~~~~~~~~
  15. python的scapy,Scapy在Python脚本中
  16. 微信小程序扫描二维码、小程序码进入的开发测试
  17. IOS- 时间格式转换问题(12小时和24小时的区别)
  18. 享受高清,索尼笔记本电脑
  19. ege寻宝挑战游戏(C、C++)(大一上游戏项目)
  20. 查看 CPU架构类型

热门文章

  1. 分段函数是不是一定初等函数_不可化为一个表达式的分段函数是不是初等函数?,分段函数是不是初等函数,那这个呢?...
  2. python列表处理函数map
  3. 拼多多 标题 html,拼多多商家运营时如何绑定关联第三方店铺展示总销量的技巧教程...
  4. PHP网站分类目录管理系统源码优客365导航源码
  5. idea配置公司的私服地址
  6. WebStorm设置px转换rem插件
  7. Tensorflow 使用cpu和gpu的区别
  8. 爱奇艺MySQL高可用方案概述
  9. 哀悼日一位老师在教室里的讲话
  10. swit - 毛玻璃效果