Spring Bean的一些Tricky问题
本文最后更新于 2025年9月13日 下午
事先声明:我没有看过什么网上的视频Spring教程,苍穹外卖项目的视线我也不是跟着教程走的,基本上就只看了第一部分讲解怎么搞CRUD和各个层的交互,后面就只看了具体模块需求,然后剩下的就自己做了。所以,我不确定我下面讲的这些东西是否已经在其他很热门的教程中已经讲过了。但是,毕竟是在实践过程中发现的问题,所以这里给大家分享一下。
Spring Bean 的循环依赖
Spring的循环依赖是一个很热门的问题,面试中也经常会被问到相关概念和解决方案。相信大家基本上都是会答以下内容:
- 一般都会会答:三级缓存
- 只对单例Bean有效,
prototype模式下的Bean会直接失效(从三个缓存Mapper的命名就能看出来)
- 只对单例Bean有效,
- 更了解的可能会回答:Spring的默认配置中把
allow-circular-references设置为false了,所以在默认情况下一见到循环依赖就会直接报错终止程序。将其设为true后才会进行三级缓存的流程。
当然,都没有毛病。不过我们平常做项目的时候,可能会在定义一个对象时调用其他库的内容,比如Lombok的的@Data。想象一下,以下代码的运行结果是什么:
1 | |
看起来没什么问题,不是吗?但是在运行过后,会出现以下报错:
1 | |
能看出来是hashCode()这个方法发生了循环。为什么?
@Data会自动为对象生成
getter/setter方法equals()方法hashCode()方法toString()方法
Bean初始化后的步骤:在Bean完全初始化后,Spring或其他组件(可能是日志、监控、或容器本身的某些后期处理)可能会调用Bean的 toString() 或 hashCode() 方法。
Lombok 的默认行为:@Data 生成的 hashCode() 和 toString() 方法会递归地包含所有字段
- 当调用
objectA.hashCode()时,Lombok生成的代码会去计算其字段objectB的哈希码。 - 这就调用了
objectB.hashCode()。 objectB.hashCode()又会去计算其字段objectA的哈希码。- 这就又调用了
objectA.hashCode()。 - …如此无限循环,直到栈溢出 (
StackOverflowError)。
toString() 方法也是同样的原理,会尝试递归地拼接所有字段的字符串表示。
因此,虽然问题并不是发生在三级缓存没有生效上,但是依然会出现无限递归的问题。
解决方法
方法一:使用 @ToString 和 @EqualsAndHashCode 注解替代 @Data
1 | |
方法二:手动实现 toString() 和 hashCode()
如果您不想依赖Lombok的魔法,可以自己手动实现这些方法,避免循环引用。
java
1 | |
也许还有其他方法,但这里只讲这两种。