【Spring】Spring中如何实现循环依赖

1、spring如何初始化一个bean(跟循环依赖没有大的关系,作为知识普及)

普通java对象的实例化过程

普通的java对象是从一个程序员写好的java对象,经过编译形成class文件,然后在运行的时候,jvm遇到new关键字的时候,就会去方法区找到该类的模板,然后创造一个该类的对象,并分配到堆上。
普通类的实例化过程

javaBean的初始化过程

首先,java的类通过类加载器编程class文件,然后spring会把这个类的所有信息加载到一个BeanDefinition当中,其中的BeanClass属性是这个类的信息,BeanClassName是这个bean的名字。
然后spring会把所有BeanDefinition丢到一个map当中,在这个时候,我们能够通过实现BeanFactoryPostProcesser接口并且重写postProccessBeanFactory方法,拿到BeanFactory对象,通过这个工厂对象,调用getBeanDefinition方法就能够得到map中对应的beanDefinition(Spring默认Bean的名字等于Class的名字进行驼峰命名法,比如UserService -> userService),然后就可以对这个beanDefinition进行操作了。
最后,Spring的bean工厂会把BeanDefinition初始化成对应的bean。所以,如果把BeanDefinition修改了,比如修改beanClass,那么就可以进行“偷天换日”的操作!
在这里插入图片描述

简单示例代码如下:

   @Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        BeanDefinition registerServiceBeanDefinition =
                configurableListableBeanFactory.getBeanDefinition("registerService");
        System.out.println(registerServiceBeanDefinition.getBeanClassName());
    }
}

运行结果:
在这里插入图片描述

2、Spring的循环依赖(重头戏!)

不使用Spring

循环依赖顾名思义就是两个对象相互依赖对方,从而形成一个死锁。举个例子,
比如现在有两个类A和B:

public class A {

	private B b = new B();
	
    public A() {
        System.out.println("Construct of A! ");
    }
}
public class B {

	private A a = new A();

    public B() {
        System.out.println("Construct of B!");
    }
}

可见,类A依赖于B,当我们实例化A时,A必须先去实例化B,而B又依赖于A,又要实例化A,如此循环往复,永远达不到终点,相当于一个死锁。

我们来运行一下测试类:

@Test
    public void test() throws Exception {
        A a = new A();
    }

运行结果:

在这里插入图片描述
可以看到,由于A和B的循环依赖造成了死循环,最终方法栈溢出,程序崩溃!

使用Spring

这一次,我们使用Spring来进行循环依赖,代码如下:

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

    public A() {
        System.out.println("Construct of A! ");
    }
}
@Component
public class B {
    @Autowired
    private A a;

    public B() {
        System.out.println("Construct of B!");
    }
}

我们来运行一下测试类:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ApplicationTest {
    @Autowired
    A a;
    
    @Test
    public void test() throws Exception {
    }
}

运行结果:

在这里插入图片描述
可以看到,使用了Spring的依赖注入,我们实现了循环依赖,类A和B都初始化成功了!那么,Spring是如何来完成这个看似实现不了的功能的呢?

Spring实现循环依赖原理

Spring注入一个Bean的过程

作为前提,这里先介绍一下Spring注入一个Bean的过程。
1、Spring会扫描到这个类需要被注入
2、Spring会判断这个类是否已经在容器的单例池中,如果已经存在,则会直接从单例池中拿到这个Bean,注入完成。
3、如果单例池没有这个Bean,Spring则会判断这个Bean是否正在被创建,如果正在被创建,则说明已经循环依赖了!这时候会判断Spring是否开启了支持循环依赖,如果开启了就把正在被创建的这个实例注入。如果没有开启则会抛出异常。
3、如果单例池中没有这个Bean,Spring会创建一个类的实例,这时候就会调用构造方法,这个实例是一个“半成品”,还没有注入其他的依赖。注意,这里有一个很重要的操作,就是把这个实例放入一个二级缓存Map中(一级缓存是单例池),表示这个类正在被创建。
4、扫描类中的依赖,然后注入这些依赖。
5、走完Bean生命周期的其他步骤,注入成功。
在这里插入图片描述

举例子分析

拿上述的类A和类B相互依赖来说,在Spring应用开启之后,Spring会扫描需要注入的类,并生成对应的BeanDefinition,最终生成对应的Bean放入单例池中。现在假如容器扫描到了类A需要被注入。
1、容器会先判断A是否已经在单例池中,由于我们之前没有注入过A,所以A不在单例池中,那么Spring会创建一个A的实例。并通知Spring类A正在创建中。
2、容器会扫描类A依赖了哪些其他类,现在扫描到了A依赖于B。
3、创建类B的实例,通知Spring类B正在创建中。
4、扫描类B的依赖,扫描到了需要依赖类A。
5、判断到类A正在创建中,所以直接拿到类A的实例注入进来。
6、层层返回,类A注入成功!

总结

循环依赖通俗来讲也就是“无限套娃”,如果不加以控制,就会造成程序的栈溢出。那么,为了解决循环依赖问题,Spring提供了一套解决方案:在创建对象A时,通知容器此对象正在被创建,那么,当循环引用时,容器需要注入A时,就会扫描到A正在被创建,然后直接把A的半成品注入,从而解决循环依赖的问题。简单的说,就是A->B->A的半成品。
最后,如果想要进一步了解Spring源码怎么实现循环依赖的,参考此文章:
阿里P8级别的spring源码关于循环引用的笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值