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

tech2022-09-19  56

Spring如何实现循环依赖

1、spring如何初始化一个bean(跟循环依赖没有大的关系,作为知识普及)普通java对象的实例化过程javaBean的初始化过程 2、Spring的循环依赖(重头戏!)不使用Spring使用SpringSpring实现循环依赖原理Spring注入一个Bean的过程举例子分析 总结

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)