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

1.什么是循环依赖

在说什么是循环依赖之前,不妨使用代码,先抛出这个异常,bug重现一下。
工程目录如下:
Spring构造注入循环依赖(BeanCreationException/BeanCurrentlyInCreationException)及其解决办法_第1张图片

注意: 本文中只需要一个配置文件: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">
        
        <property name="role" ref="role"/>
    bean>
    <bean id="role" class="com.lavendor.model.Role">
        
        <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的循环依赖问题,基于注解的以后探讨。如有不当之处,敬请指正。

你可能感兴趣的:(java,Spring,Spring注入)