lz今天在处理Spring事务的时候遇到一个有意思的坑。

大概就是我在业务层中一个Service要调用自身的其他方法,而它本身又开启了Spring声明式事务,很明显,如果直接用this执行其他要调用的方法,将会导致Spring事务失效(原因:事务本质是Aop,而Aop通过代理的方式完成,this指向会破坏代理关系从而导致事务失效)。

解决自调引起的事务失效大概有三种方法:

  • 容器中注入容器本身---但是这会引起依赖循环,也就是后面要讲的。
  • 采用AopContext获取当前的代理对象。
  • 使用Spring的编程事务
于是lz采取最简便的第一种方式,但是运行便遇到了依赖循环的报错:
1
2
3
┌─────┐
mediaFileServiceImpl defined in file [D:\MyCode\JavaCloud\xuecheng-plus\xuecheng-plus-media\xuecheng-plus-media-biz\target\classes\work\echoes\media\biz\service\impl\MediaFileServiceImpl.class]
└─────┘

自然而然的想到了高版本Spring默认关闭了依赖循环,于是在配置中打开:

1
2
3
spring:
main:
allow-bean-definition-overriding: true

但是运行依旧是同样的报错,那么为什么不能解决依赖循环呢?

首先从源头开始,Spring采用三级缓存的方式来解决依赖循环:

  • singletonObjects, 一级缓存
  • earlySingletonObjects, 二级缓存
  • singletonFactories 三级缓存
容器创立流程(参考地址):
  1. 对象A要创建到Spring容器中,从一级缓存singletonObject获取A,不存在,开始实例化A,最终在三级缓存singletonObjectFactory添加(A,A的函数式接口创建方法),这时候A有了自己的内存地址
  2. 设置属性B,B也从一级缓存singletonObject获取B,不存在,开始实例化B,最终在三级缓存singletonObjectFactory添加(B,B的函数式接口创建方法),这时候B有了自己的内存地址
  3. B中开始给属性A赋值,此时会找到三级缓存中的A,并将A放入二级缓存中。删除三级缓存
  4. B初始化完成,从三级缓存singletonObjectFactory直接put到一级缓存singletonObject,并删除二级和三级缓存的自己
  5. A成功得到B,A完成初始化动作,从二级缓存中移入一级缓存,并删除二级和三级缓存的自己
  6. 最终A和B都进入一级缓存中待用户使用
那么源头可能就是我容器注入的方式不对了。回过头来看,我通过**lombok**的注解 `@RequiredArgsConstructor` 配合 final属性,一键完成注入,而这个注解本身就是通过构造器注入的方式注入,那么结合上面的分析,不难发现构造器不能构造自身,于是导致了无法在第三级的缓存总生成未完全的容器对象,进而导致了三级缓存机制无法解决依赖循环问题。

了解到根本原因,那么好修改了,只要把这种构造器注入的方式改为setter注入或者@autowired注入,便可以解决问题。