Spring构造注入循环依赖(BeanCreationException/BeanCurrentlyInCreationException)及其解决办法

1 篇文章 0 订阅
1 篇文章 0 订阅
1.什么是循环依赖

在说什么是循环依赖之前,不妨使用代码,先抛出这个异常,bug重现一下。
工程目录如下:
在这里插入图片描述

注意: 本文中只需要一个配置文件:spring.xml,两个bean:User和Role。

User.java代码如下:

public class User {
    private Role role;

    public User(Role role) {
        this.role = role;
    }

    public void sayUser(){
        System.out.println("this is user model");
    }
}

Role.java:

public class Role {
    private User user;

    public Role(User user) {
        this.user = user;
    }

    public void sayRole(){
        System.out.println("this is Role model");
    }
}

spring.xml:

<!--循环依赖-->
    <bean id="user" class="com.lavendor.model.User">
        <constructor-arg ref="role"/>
    </bean>
    <bean id="role" class="com.lavendor.model.Role">
        <constructor-arg ref="user"/>
    </bean>

Test.java:

public class Test {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
        User user = (User) ctx.getBean("user");
        user.sayUser();
    }
}

运行此main方法,会抛出如上BeanCurrentlyInCreationException,BeanCreationException异常。
至此,bug已经重现,下面分析下为何产生此bug。

2.为什么会产生循环依赖

简单来说就是,实例化user时,需要引用role,但是当前引用的role正在实例化,要完成实例化role,必须要user完成实例化。如此互相牵制,形成一个依赖循环,故而抛出异常。
为什么为产生循环依赖呢?在此之前,有必要先理清一下springbean的加载过程,参考文章:spring bean的初始化过程

下面是其中几步的实例化步骤:

1.调用bean的构造方法初始化
2.设置bean中属性值(调用setter方法)

在本例中,bean user注入方式使用的是构造方法注入,而user的加载方式是先调用构造方法,构造方法初始化又依赖于bean role的初始化结果,而bean role也是同样依赖于user的初始化结果,如此,则构成循环依赖。

3.如何解决循环依赖

上面简单分析了构成循环依赖的原因:因为使用的是构造注入,在初始化的时候即造成循环依赖。由此可以想到,使用setter方法注入会不会产生循环依赖呢?
修改代码如下:
1.去掉User.java Role.java中的构造方法,生成属性的setter方法:

/**
* Role类
*/
public class Role {
    private User user;

    public void setUser(User user) {
        this.user = user;
    }

    /*public Role(User user) {
        this.user = user;
    }*/

    public void sayRole(){
        System.out.println("this is Role model");
    }
}

/**
* User类
*/
public class User {
    private Role role;

    public void setRole(Role role) {
        this.role = role;
    }

    /*public User(Role role) {
        this.role = role;
    }*/

    public void sayUser(){
        System.out.println("this is user model");
    }
}

2.修改spring.xml中bean的注入方式为setter注入:

    <!--循环依赖-->
    <bean id="user" class="com.lavendor.model.User">
        <!--<constructor-arg ref="role"/>-->
        <property name="role" ref="role"/>
    </bean>
    <bean id="role" class="com.lavendor.model.Role">
        <!--<constructor-arg ref="user"/>-->
        <property name="user" ref="user"/>
    </bean>

最后执行Test.java 程序运行正常,问题得以解决。

4.setter方法注入如何解决循环依赖

前文提到过bean加载顺序中步骤2可知,在初始化bean 时,先调用构造函数,再调用setter方法。spring可以在调用了默认构造函数之后,并且为填充属性值之前的中间,向外暴露一个可调用此正在创建中的bean的单例工厂方法,以便于拿到此bean的引用,那么即可完成循环的实例化。

由此可知,setter注入解决循环依赖原理大致如下:
1.user初始化,先调用默认的构造函数(无参数)创建一个实例,并未完成其属性值赋值等初始化工作。并且其他bean能够调用它;
2.role初始化,调用默认的构造函数(无参数)创建一个实例,并未完成其属性值赋值等初始化工作,并且其他的bean能够调用它;
3.user能够拿到 role实例的引用,填充自身依赖属性,完成初始化;而后role也以同样的方式完成自身的初始化。

本文只是探讨了基于xml的循环依赖问题,基于注解的以后探讨。如有不当之处,敬请指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值