spring循环依赖解决办法

Spring循环依赖的解决办法!包含代码讲解!!!

大家面试相信遇到过这么一个问题。

面试官问:你知道spring的循环依赖吗,可以讲一下吗?

我:这个我熟啊,循环依赖就是spring构造一个bean对象A,但是A里面有一个属性为B,所以spring回去创建我们的B,但是B对象创建的时候,填充属性的时候又发现的他有个属性为A,这就造成了我们的循环依赖。我们可以使用三级缓存来解决它。

面试官:你可以走了~~~

我:好嘞!

哈哈哈,一个小插曲,虽然说简洁的说确实是这样,但是你这样说肯定是不行的,像是在背八股文。

首先,你要回答这个问题你必须得了解spring的bean它的一个生命周期。还有spring的依赖注入的方式。

首先spring的依赖注入大概可以分为两点,一个是set注入,另外一个是构造器注入。spring可以解决掉我们的setter注入的方式,还有就是单例模式下的循环依赖,构造器注入和多例模式都不行。

spring的生名周期大概又可以分为四点,首先是createBeanInstance实例化,接着populateBean属性赋值,然后是initializeBean初始化,最后销毁。

而且还得提到一下spring的三级缓存

//一级缓存 主要是用于存放完全初始化好的bean对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//二级缓存 主要是用于存放 还未填充属性的最原始的bean对象(用于解决循环依赖)
private final Map<String, Object> EarlySingletonObjects = new ConcurrentHashMap<>(16);
//三级缓存 主要是存放ObjecttFactory bean的工厂对象 (用于解决循环依赖)
private final Map<String, Object> singletonFactories = new ConcurrentHashMap<>(16);

我们的spring在启动的时候会去扫描看哪些需要注册bean 当扫面后会把它变成一个BeanDefintion然后存入一个BeanDefintion Map中,然后遍历这个Map,做一些验证,比如说是否是单例,是否懒加载等等,然后他会首先去获取这个bean是否已经存在于单例池中,有没有被提交前暴露,然后他会去判断哪个构造方法最优。接着我们的Spring就会通过反射去实例化一个JAVA对象,对这个bean做一些初始化的操作,比如说是否需要去合并BeanDefintion,然后他就会判断我们Spring容器是否支持循环依赖,支持的话,他就会暴露原始的bean对象到二级缓存中,接着会将bean的一个工厂类暴露到三级缓存中。

当A对象填充属性的时候,发现需要B就会依次将bean的工厂对象暴露到三级缓存中然后去实例化B,而B填充属性时,发现需要A,就会去单例池获取A,发现没有,就会去二级缓存获取,但是二级缓存中的A已经暴露到了三级缓存的工厂中,所以他会去三级缓存中取这个半成品A,然后B的bean创建完成,放入单例池中,A再去填充属性。

话不多说,上图:

spring循环依赖解决办法_第1张图片

上代码:

下述为循环依赖的代码示列:

//循环依赖
@Component
public class A {

    @Autowired
    private  B b;
    
    public void hello(){
        System.out.println("helloA!!!");
    }
}
@Component
public class B {

    @Autowired
    private A a;
    
    public void hello(){
        System.out.println("helloB!!!");
    }
}
@SpringBootTest
class DemoApplicationTests {
 @Autowired
 A a;
  @Test
    void contextLoads() {
      a.hello();
  }
}
//报错
Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  a
↑     ↓
|  b (field private com.example.demo.test.A com.example.demo.test.B.a)
└─────┘

解决办法:

主要的解决方法有三种,一种是setter注入,第二种是@Lazy,第三种是@PostConstruct

1.@Lazy 在需要注入的属性前,表示延迟注入

@Component
public class A {
    
    @Autowired
    private  @Lazy B b;
    public B getB() {
        return b;
    }

    public void hello(){
        System.out.println("helloA!!!");
    }
}
@Component
public class B {

    @Autowired
    private @Lazy A a;

    public void hello(){
        System.out.println("helloB!!!");
    }
}

2.使用setter/filed注入,spring的官方推荐也是使用的该方法。

public class A {

    private   B b;

    public B getB() {
        return b;
    }
    @Autowired
    public void setB(B b) {
        this.b = b;
    }

    public void hello(){
        System.out.println("helloA!!!");
    }
}
@Component
public class B {
    
    private  A a;

    public A getA() {
        return a;
    }
    
    @Autowired
    public void setA(A a) {
        this.a = a;
    }

    public void hello(){
        System.out.println("helloB!!!");
    }
}

3.使用@PostConstruct,@PostConstruct注解的方法将会在A注入完成后被自动调用。

@Component
public class A {
    @Autowired
    private   B b;

    public B getB() {
        return b;
    }

    @PostConstruct
    public void init(){
        b.setA(this);
    }
    public void hello(){
        System.out.println("helloA!!!");
    }
}

public class B {

    private  A a;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }

    public void hello(){
        System.out.println("helloB!!!");
    }
}

终章

方法肯定不止这三种,小编在这里只是举例,还有很多种方法可以解决,但是当出现这种问题的时候,一般我们要去想办法,如何让我们的代码不去循环依赖。我觉得主要的是反思自己的代码设计,而不是去解决循环依赖。

你可能感兴趣的:(spring,java,面试)