Spring循环依赖以及解决方法

什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
Spring循环依赖以及解决方法_第1张图片
Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。
1. filed 属性注入的循环依赖代码

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

Spring 解决这个问题是通过 Java 的引用传递,以及提前暴露对象到三级缓存中和延后设置 field 属性来实现的。

如果把 bean 的创建过程简述为 2 个步骤的话:第一步是创建一个对象(通过反射);第二步是给对象填充属性。
Spring 在创建 bean 的时候并不是等它完全完成,而是将创建中的 bean 提前曝光(即加入到 singletonFactories
三级缓存中),当下一个 bean 创建的时候需要依赖此 bean ,则从三级缓存中获取。


Spring 解决field属性注入引发循环依赖的过程:

如果把 bean 的创建过程简述为 2 个步骤的话:第一步是创建一个对象(通过反射);第二步是给对象填充属性
Spring 在创建 bean 的时候并不是等它完全完成,而是将创建中的 bean 提前曝光(即加入到 singletonFactories 三级缓存中),当下一个 bean 创建的时候需要依赖此 bean ,则从三级缓存中获取。

1.先创建A对象,A对象创建完后放到Spring的三级缓存;
2.此时A对象的b属性为空,需要注入b属性,先去三级缓存找B对象,没有找到,此时触发B对象的创建流程。
3.创建B对象,并将创建出来的B对象放进三级缓存中,
4.B对象的b属性为空,需要填充a属性,然后在三级缓存中找到A对象注入属性a,
5.最后再将A对象的b属性进行填充,从缓存中顺利拿到B对象,进行属性注入,循环依赖到此解决。

spring的一二三级缓存:
singletonObjects:
:一级缓存,用于存放完整的bean,从该缓存取到的bean可以直接使用;
earlySingletonObjects::二级缓存,存放提前暴露的bean,bean是不完整的,未完成属性注入和init方法,用于解决循环依赖。
singletonFactories:三级缓存,对初始化后的bean完成AOP代理操作,bean初始化完成之后才生成代理,而不是实例化之后就生成代理,保证了bean的生命周期。

一级缓存和二级缓存比较容易理解,因为只有一级缓存的话,里边既有完整的 bean,也会有尚未初始化的 bean,这样在获取到尚未初始化的 bean 进行调用的时候,就会出现问题。

如果注入的都是普通对象的话,一级缓存和二级缓存已经能解决循环依赖问题。

但是如果是代理对象的话,如果只有一级缓存和二级缓存,那在对象实例化之后就必须要马上创建一个代理对象,这样就违背了 Spring 结合 AOP跟 Bean 的生命周期的设计,Spring 结合 AOP 跟 Bean 的生命周期本身就是通过
AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,所以 Spring
才会通过三级缓存来保证创建普通对象和创建代理对象时 bean 的生命周期流程统一。

怎么解决构造器注入引发的循环依赖问题?
我们可以通过两种方式来解决构造器注入引发的循环依赖问题:
1.第一种是通过 @Scope 的 proxyMode 属性来设置类的代理模式;
2.第二种是使用 @Lazy 注解。
1.通过 @Scope 的 proxyMode 属性来设置类的代理模式代码示例:

@Service
public class A {

    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }

}

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) // 使用 CGLIB 动态代理
@Service
public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

}

@Scope 注解是 Spring IOC 容器中的一个作用域,默认为 singleton(单例)。通过 Scope 中的 proxyMode 属性可以设置类的代理模式,

DEFAULT 不使用代理
NO 不使用代理
INTERFACES
使用 JDK 动态代理
TARGET_CLASS 使用 CGLIB 动态代理

这里我们不对 @Scope 做过多介绍,上边代码中因为 B 类没有实现接口,不能使用 JDK 动态代理,所以这里使用的是 CGLIB 动态代理。
使用 @Lazy 注解的代码示例:

@Service
public class A {

    private B b;

    @Autowired
    public A(@Lazy B b) {
        this.b = b;
    }

}

@Service
public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

}

@Lazy 注解用于标识 bean 是否需要延迟加载。

当我们通过上述两种方式进行代码改动后,再次启动程序就不会抛出异常了。
那,这两种方式又是如何解决循环依赖问题的呢?

其实它们 都是通过动态代理来避免了循环依赖。
我们再来描述一下 解决循环依赖的过程:
1.创建 A 时需要 B ,发现 B 是可以延迟加载或者是指定了代理模式的;
2.创建一个 B 类的代理类 Bproxy ;
3.通过 Bproxy 创建 A(此时 A 的依赖关系已经变成了 A 依赖 Bproxy);
4.再创建 B 时,A 已经存在,所以 B 也成功创建。
Spring循环依赖以及解决方法_第2张图片

Spring循环依赖以及解决方法_第3张图片

循环依赖视频

循环依赖

你可能感兴趣的:(spring,链表,数据结构,dfs)