专栏首页java 成神之路spring 循环依赖注入

spring 循环依赖注入

什么是循环依赖

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。

spring 中循环依赖注入分三种情况

  1. 构造器循环依赖
  2. setter方法循环注入 2.1 setter方法注入 单例模式(scope=singleton) 2.2 setter方法注入 非单例模式

我们首先创造3个互相依赖的bean类

A.java

public class A {
    private B b;
    
    public A(){}
    public A(B b){ this.b = b; }

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

    public void hello(){ b.doHello(); }
    
    public void doHello(){
        System.out.println("I am A");
    }
}

B.java

public class B {
    private C c;
    
    public B(){}
    public B(C c){ this.c = c; }

    public C getC() { return c; }
    public void setC(C c) { this.c = c; }
    
    public void hello(){ c.doHello(); }
    
    public void doHello(){
        System.out.println("I am B");
    }
}

C.java

public class C {
    private A a;
    
    public C(){}
    public C(A a){ this.a = a; }

    public A getA() { return a; }
    public void setA(A a) { this.a = a; }
    
    public void hello(){ a.doHello(); }
    
    public void doHello(){
        System.out.println("I am C");
    }
}

执行类SpringMain.java

public class SpringMain {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean-circle.xml");
        A a = A.class.cast(ac.getBean("a"));
        a.hello();
    }
}

1. 构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

  • 在创建A类时,构造器需要B类,那将去创建B,
  • 在创建B类时又发现需要C类,则又去创建C,
  • 最后在创建C时发现又需要A;从而形成一个环,没办法创建。

Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

<bean id="a" class="cn.com.infcn.test.A">
    <constructor-arg ref="b" />
</bean>
<bean id="b" class="cn.com.infcn.test.B">
    <constructor-arg ref="c" />
</bean>
<bean id="c" class="cn.com.infcn.test.C">
    <constructor-arg ref="a" />
</bean>

执行SpringMain.main()方法报错

2. setter方法循环注入

setter循环依赖:表示通过setter注入方式构成的循环依赖。 对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

2.1 setter方法注入 单例模式 (scope="singleton")

具体步骤如下:

  1. Spring容器创建单例“A” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“A” 标识符放到“当前创建Bean池”;然后进行setter注入“B”;
  2. Spring容器创建单例“B” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“B” 标识符放到“当前创建Bean池”,然后进行setter注入“C”;
  3. Spring容器创建单例“C” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“C” 标识符放到“当前创建Bean池”,然后进行setter注入“A”;进行注入“A”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;
  4. 最后在依赖注入“B”和“A”,完成setter注入。
<bean id="a" class="cn.com.infcn.test.A">
    <property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B">
    <property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C">
    <property name="a" ref="a"></property>
</bean>

执行SpringMain.main()方法打印如下:

I am B

2.2 非单例 setter 循环注入(scope=“prototype”)

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

<bean id="a" class="cn.com.infcn.test.A" scope="prototype">
    <property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B" scope="prototype">
    <property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C" scope="prototype">
    <property name="a" ref="a"></property>
</bean>

执行SpringMain.main()方法报错

模拟 Spring 单例 setter 循环依赖实现

创建一个ObjectFactory.java

public class ObjectFactory<T> {

    private String className;
    private T t;

    public ObjectFactory(String className, T t) {
        this.className = className;
        this.t = t;
    }

    public T getObject() {
        //如果该bean使用了代理,则返回代理后的bean,否则直接返回bean
        return t;
    }
}

模拟实现类

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    // 单例Bean的缓存池
    public static final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
    //单例Bean在创建之初过早的暴露出去的Factory,为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了。
    public static final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
    //执行了工厂方法生产出来的Bean,总不能每次判断是否解决了循环依赖都要执行下工厂方法吧,故而缓存起来。
    public static final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
    
    public static void main(String[] args) {
        A a = (A)getA();
        a.hello();
        
        a = (A)getA();
        a.hello();
        
        B b = (B)getB();
        b.hello();
        
        C c = (C)getC();
        c.hello();
    }
    
    //模拟 spring中 applicationContext.getBean("a")
    public static Object getA(){
        String beanName = "A";
        Object singletonObject = getSingleton(beanName);
        if(singletonObject == null){
            A bean = new A();
            singletonFactories.put(beanName, new ObjectFactory<A>(beanName, bean));
            bean.setB((B)getB());
            addSingleton("A", bean);
            return bean;
        }
        return singletonObject;
    }
    
    //模拟 spring中 applicationContext.getBean("b")
    public static Object getB(){
        String beanName = "B";
        Object singletonObject = getSingleton(beanName);
        if(singletonObject == null){
            B bean = new B();
            singletonFactories.put(beanName, new ObjectFactory<B>(beanName, bean));
            bean.setC((C)getC());
            addSingleton(beanName, bean);
            return bean;
        }
        return singletonObject;
    }
    
    //模拟 spring中 applicationContext.getBean("c")
    public static Object getC(){
        String beanName = "C";
        Object singletonObject = getSingleton(beanName);
        if(singletonObject == null){
            C bean = new C();
            singletonFactories.put(beanName, new ObjectFactory<C>(beanName, bean));
            bean.setA((A)getA());
            addSingleton(beanName, bean);
            return bean;
        }
        return singletonObject;
    }
    
    public static void addSingleton(String beanName, Object singletonObject){
        singletonObjects.put(beanName, singletonObject);
        earlySingletonObjects.remove(beanName);
        singletonFactories.remove(beanName);
    }
    
    public static Object getSingleton(String beanName){
        Object singletonObject = singletonObjects.get(beanName);
        if(singletonObject==null){
            synchronized (singletonObjects) {
                singletonObject = earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        earlySingletonObjects.put(beanName, singletonObject);
                        singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}
  • singletonObjects:单例Bean的缓存池
  • singletonFactories:单例Bean在创建之初过早的暴露出去的Factory,为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了。
  • earlySingletonObjects:执行了工厂方法生产出来的Bean,总不能每次判断是否解决了循环依赖都要执行下工厂方法吧,故而缓存起来。

getA()方法、 getB()方法、 getC()方法 是为了模拟applicationContext.getBean() 方法获取bean实例的。因为这里省略了xml配置文件,就把getBean() 方法拆分了三个方法。

这里的ObjectFactory有什么用呢,为什么不直接保留bean 实例对象呢? spring源码中是这样实现的如下代码:

从源码中可以看出,这个ObjectFactory的作用是:如果bean配置了代理,则返回代理后的bean。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • NIO 之 Channel

    java404
  • spring bean 基于xml的4中初始化方法

    java404
  • InetAddress 解析

    java404
  • Spring源码学习笔记(9)——AOP

    AOP的定义及一些术语相信大家已经很熟悉了,这里不再赘述。下面演示基于注解的Spring AOP开发。

    张申傲
  • 常用开源框架中设计模式使用分析-单例设计模式(Singleton Pattern)

    单例模式是一种创建型模式,单例模式提供一个创建对象的接口,但是多次调用该接口返回的是同一个实例的引用,目的是为了保证只有一个实例,并且提供一个访问这个实例的统一...

    加多
  • 万字长文,助你深度遨游Spring循环依赖源码实现!

    最近有很多读者面试的时候都被问到了有关于Spring三级缓存的解决方案,很多读者在面试受挫之后,试着自己去读源码,试着去跟断点又发现一层套一层,一会自己就懵了,...

    止术
  • Spring Ioc 之 Bean的加载(二)

    Spring 只处理单例模式下得循环依赖,对于原型模式的循环依赖直接抛出异常。

    大王叫下
  • Spring 中控制 Bean 生命周期的几种方式及 BeanPostProcessor 执行原理

    这两个类都是接口,其中 InitializingBean 有一个抽象方法 afterPropertiesSet ,DisposableBean 有一个抽象方法 ...

    wsuo
  • Spring5 源码学习 (9) doGetBean 概述

    接上回,AbstractApplicationContext#refresh调用AbstractApplicationContext#finishBeanFac...

    Coder小黑
  • 你知道Spring是怎么将AOP应用到Bean的生命周期中的吗?

    在上篇文章中(Spring中AOP相关的API及源码解析,原来AOP是这样子的)我们已经分析过了AOP的实现的源码,那么Spring是如何将AOP应用到Bean...

    程序员DMZ

扫码关注云+社区

领取腾讯云代金券