大白话理解String的Bean的循环依赖问题(含代码例子)

文章详细阐述了Spring框架中Bean的循环依赖问题,包括单例Bean的set注入不会产生循环依赖,多例Bean的set注入会产生循环依赖,以及单例Bean的构造注入会产生的循环依赖。通过三级缓存机制,Spring解决了单例Bean的循环依赖问题,而使用@Lazy注解可以延迟初始化,解决循环依赖。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

        什么是Bean的循环依赖

        单例Bean的set注入不会产生循环依赖 (多个都是单例)

         多例Bean的set注入会产生循环依赖(多个都是多例)

        单、多例Bean的set注入不会产生的循环依赖

        单例Bean的构造注入会产生的循环依赖

        三级缓存 

        总结

什么是Bean的循环依赖

        在Spring容器中,当存在两个或多个Bean之间相互依赖时,就会出现循环依赖的问题。如果没有正确地处理这种情况,则会发生自引用异常,导致应用程序无法启动。

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。我想创建出来得初始化你,而你的初始化又需要我,这样子都别玩了!!!

在Spring中的循环依赖主要分为三大类:

        单例Bean的set注入产生的循环依赖(全是单例)(不会报错)

        单、多例Bean的set注入产生的循环依赖(一单一多)(不会报错)

        多例Bean的set注入产生的循环依赖(全是多例)(会报错)

        单例Bean的构造注入产生的循环依赖 (会报错)


单例Bean的set注入不会产生循环依赖 (多个都是单例)

singleton + setter模式下的循环依赖是没有任何问题的,这里得力于两个特性:

        1.单例Bean采用单例模式产生,其在整个Spring容器中是唯一的

        2.在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean 实例化之后,不等属性赋值,马上进行"曝光"(可以理解为申请了空间有了地址,但是里面还没东西)。

        3.set注入可以实现实例化和属性赋值的操作分离开,从而可以实现"曝光"操作。

         通过曝光、set注入的特性,可以让我们对循环依赖的过程中的矛盾问题耍个小聪明,我要你的东西,你给了我个假的,你要我的东西时候,就拿到我真的东西,最后再悄悄咪咪把我拿到的假东西变为了真东西。

给我仔细理解这个情况,理解了这个就明白后面三种为什么会导致循环依赖报错了 


 多例Bean的set注入会产生循环依赖(多个都是多例)

如果两个都是多列Bean,就会报错,原因是多例Bean每次获取的对象都不是同一个

之所以第一种情况可以避免陷入循环依赖的死循环,而这种情况不行是因为第一种情况是每个Bean只有唯一的一个对象,你看,我一个Bean被你通过"曝光"的方式来混过去了,自愿被创建了出来,在依赖它的其它Bean就可以拿着这个对象来完善自身的属性,从而避免了死循环。但是你看,由于多例Bean无法保证创建的Bean是同一个,所以会导致每次通过"曝光"附上属性值的Bean不是同一个,所以对于每次指向的Bean都是一个空Bean,每一次都要重新曝光,一直循环下去。 


单、多例Bean的set注入不会产生的循环依赖

        针对上述多例Bean的循环依赖问题,我们不难发现,它缺的是一个"天选之子"来打破僵局,只要有一个确定了,能被别人唯一依赖了,就可以摆脱死循环下去了。因为单例的Bean的对象只有一个你再继续调该单例Bean,由于它是同一个,也就说说它的属性都已经赋好值了,就不会再调回来再创建多例Bean


单例Bean的构造注入会产生的循环依赖

        这种情况会导致循环依赖异常,主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的 

要知道,我们单例Bean的set注入之所以循环依赖不会报错是围绕这”曝光“这种方式来让对方能够提前获取我的一个空对象,后面再把该空对象填充好就好了,但是这种方式的实现前提在于它的实例化过程和属性赋值的过程是分离的才行,但是构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,所以就不能提前"曝光",因此就over啦

无论你单/多例,因为在构造注入中,你根本无法实例化对象,所以说你连曝光都做不到 


三级缓存 

        单例对象的缓存:key存储bean名称,value存储Bean对象【一级缓存】

        早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象(空架子)【二级缓存】

        单例工厂缓存:key存储bean名称,value存储该Bean对应的被提前曝光的ObjectFactory对象【三级缓存】

来看看源码(看不懂就记结论去~):

         从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题

总结

        当Bean是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

        而在SpringBoot中使用@Lazy注解可以解决循环依赖问题,@Lazy注解的原理是通过将Bean组件延迟初始化,由Spring框架维护一个特殊的代理类作为要延迟的Bean的替身,然后在程序访问该Bean时,Spring框架实际上会去调用这个代理类去获取相应的Bean实例。其实本质和我们上面提到的"曝光"的效果都是差不多的啦 

### Java Bean 的概念 Java Bean 是一种特殊的 Java 类,它遵循特定的约定和规则。本质上,Java Bean 就是一个普通的 Java 类,但它具有一些额外的特点使其更易于管理和使用[^1]。 #### 特点 1. **私有字段**:所有的属性都应该是私有的,通过 getter 和 setter 方法来访问这些属性。 2. **无参构造器**:必须提供一个公共的无参构造函数,以便框架能够实例化该类。 3. **序列化支持**:通常实现 `Serializable` 接口,使得对象可以在不同环境中传递或保存。 4. **可重用性**:由于其标准化的设计,Java Bean 可以轻松地在不同的应用中重复使用。 例如,下面展示了一个简单的 Java Bean: ```java import java.io.Serializable; public class Person implements Serializable { private String name; private int age; // 无参构造器 public Person() {} // Getter 和 Setter 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 在这个例子中,`Person` 类满足了 Java Bean 的基本要求:具有私有字段、getter 和 setter 方法以及一个无参构造器。 --- ### Spring 中的 BeanSpring 框架中,“Bean”的义更加广泛。它是 Spring 容器中的一个组件,由容器负责创建、配置和管理生命周期[^2]。换句话说,Spring 中的 Bean 不仅限于传统的 Java Bean,还可以是任何被 Spring 管理的对象。 以下是 Spring 中定义 Bean 的几种方式之一,使用 `@Bean` 注解的方式[^4]: ```java @Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } } ``` 在这里,`myService()` 方法返回的对象会被 Spring 容器识别并管理为一个 Bean。 --- ### 动态代理与 Bean 的关系 当涉及到复杂的业务逻辑时,Spring 使用动态代理技术增强 Bean 的功能。比如事务管理、日志记录等功能可以通过代理机制透明地附加到 Bean 上,而无需修改原始代码[^5]。 以下展示了如何利用 JDK 动态代理为接口生成代理对象: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Service { void execute(); } class RealService implements Service { public void execute() { System.out.println("Executing..."); } } class DynamicProxyHandler implements InvocationHandler { private Object target; public DynamicProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before execution"); Object result = method.invoke(target, args); System.out.println("After execution"); return result; } } public class ProxyExample { public static void main(String[] args) { Service realService = new RealService(); Service proxyInstance = (Service) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new DynamicProxyHandler(realService)); proxyInstance.execute(); } } ``` 此代码片段说明了如何通过动态代理扩展 Bean 的行为而不改变其内部结构。 --- ### 总结 - Java Bean 是一种标准的 Java 类,具备私有属性、公有 getter/setter 方法以及无参构造器等特点。 - 在 Spring 框架中,Bean 是指由 Spring 容器管理的对象,它可以是传统意义上的 Java Bean 或其他类型的组件。 - 动态代理技术允许我们在不修改原有代码的情况下增强 Bean 的功能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学徒630

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值