菜鸟笔记
提升您的技术认知

解决循环依赖的方法-ag真人游戏

一、什么是循环依赖呢?

类a依赖类b,类b也依赖类a,这种情况就会出现循环依赖。

bean a → bean b → bean a

上面是比较容易发现的循环依赖,也有更深层次的循环依赖。

bean a → bean b → bean c → bean d → bean e → bean a

二、spring 的循环依赖

当spring上下文在加载所有的bean时,会尝试按照他们他们关联关系的顺序进行创建。如果不存在循环依赖时,例如:

bean a → bean b → bean c

spring会先创建bean c,再创建bean b(并将bean c注入到bean b中),最后再创建bean a(并将bean b注入到bean a中)。

但是,如果我们存在循环依赖,spring上下文不知道应该先创建哪个bean,因为它们依赖于彼此。在这种情况下,spring会在加载上下文时,抛出一个beancurrentlyincreationexception。

当我们使用构造方法进行注入时,会遇到这种情况。因为它是上下文加载就被要求注入。

 

三、举个栗子

用户类需要调用组织类中的方法,于是通过构造方法注入组织类。

@service
public class userservice {
    
    private final departmentservice departmentserivce;
    /**
     * 通过构造方法注入departmentservice类
     */
    @autowired
    public userserivce(departmentservice departmentservice) {
        this.departmentservice = departmentservice;
    }
    public list list() {
        retern departmentservice.list();
    }    
}

而组织类也刚好需要调用用户类里的方法,于是它也通过构造方法注入用户类。

@service
public class departmentservice {
    
    private final userservice userserivce;
    /**
     * 通过构造方法注入userservice类
     */
    @autowired
    public departmentserivce(userservice userserivce) {
        this.userserivce = userserivce;
    }
    
}

这种情况程序在编译时,就会报下面的错误

description:
the dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  userservice defined in file [d:\java\ideaprojects\userservice.class]
↑     ↓
|  departmentservice defined in file [d:\java\ideaprojects\departmentservice.class]
└─────┘

‎现在我们可以为测试编写一个配置类,让我们称之为‎‎testconfig‎‎,它指定要扫描组件的基本包。让我们假设我们的bean在包"‎‎com.baeldung.circulardependency‎‎"中定义:‎

@configuration
@componentscan(basepackages = { "com.baeldung.circulardependency" })
public class testconfig {
}

最后,我们可以编写一个 junit 测试来检查循环依赖关系。测试可以为空,因为在上下文加载期间将检测到循环依赖关系。‎

@runwith(springjunit4classrunner.class)
@contextconfiguration(classes = { testconfig.class })
public class circulardependencytest {
    @test
    public void givencirculardependency_whenconstructorinjection_thenitfails() {
        // empty test; we just want the context to load
    }
}

‎如果您尝试运行此测试,您将得到以下异常:‎

beancurrentlyincreationexception: error creating bean with name 'userservice':
requested bean is currently in creation: is there an unresolvable circular reference?

四、解决办法

4.1 重新设计

当出现这种循环依赖时,很可能是在设计方面存在问题,没有把每个类的职责很好的区分开。应该尝试正确重新设计组件,以便其层次结构设计良好,并且不需要循环依赖项。‎

如果无法重新设计,那么可以考虑其他解决办法。

4.2 使用 setter/field 方法注入

上面说到,只有构造方法是在上下文加载时就要求被注入,容易出现依赖循环。所以可以用其他的方式进行依赖注入,setter 和 field 方法注入与构造方法不同,它们不会在创bean时就注入依赖,而是在被需要时才注入。

setter方法注入

@service
public class userservice {
    
    private departmentservice departmentserivce;
   
    @autowired
    public void setdepartmentserivce(departmentservice departmentservice) {
        this.departmentservice = departmentservice;
    }
    public list list() {
        retern departmentservice.list();
    }    
}

另一个类也是同理使用setter方法注入

field方法注入

(使用@autowired)

@service
public class userservice {
    
    @autowired
    private departmentservice departmentserivce;
    public list list() {
        retern departmentservice.list();
    }    
}

4.3 使用 @lazy

解决spring 循环依赖的一个简单方法就是对一个bean使用延时加载。也就是说:这个bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。
我们对 userservice 进行修改,结果如下:

@service
public class userservice {
    
    private departmentservice departmentserivce;
    @autowired
    public userserivce(@lazy departmentservice departmentservice) {
        this.departmentservice = departmentservice;
    }
    public list list() {
        retern departmentservice.list();
    }    
}

如果你现在运行测试,你会发现之前的错误不存在了。

4.4 使用 @postconstruct

打破循环的另一种方式是,在要注入的属性(该属性是一个bean)上使用 @autowired,并使用@postconstruct 标注在另一个方法,且该方法里设置对其他的依赖。

我们的bean将修改成下面的代码:

@service
public class userservice {
 
    @autowired
    private departmentservice departmentservice;
 
    @postconstruct
    public void init() {
        departmentservice.setuserservice(this);
    }
 
    public departmentservice getdepartmentservice() {
        return departmentservice;
    }
}
@service
public class departmentservice {
 
    private userservice userservice;
     
    private string message = "hi!";
 
    public void setuserservice(userservice userservice) {
        this.userservice = userservice;
    }
     
    public string getmessage() {
        return message;
    }
}

现在我们运行我们修改后的代码,发现并没有抛出异常,并且依赖正确注入进来。

4.5 实现applicationcontextaware and initializingbean接口

如果一个bean实现了applicationcontextaware,该bean可以访问spring上下文,并可以从那里获取到其他的bean。实现initializingbean接口,表明这个bean在所有的属性设置完后做一些后置处理操作(调用的顺序为init-method后调用);在这种情况下,我们需要手动设置依赖。

@service
public class userservice implements applicationcontextaware, initializingbean {
 
    private departmentservice departmentservice;
 
    private applicationcontext context;
 
    public departmentservice getdepartmentservice() {
        return departmentservice;
    }
 
    @override
    public void afterpropertiesset() throws exception {
        circb = context.getbean(departmentservice.class);
    }
 
    @override
    public void setapplicationcontext(final applicationcontext ctx) throws beansexception {
        context = ctx;
    }
}
public class departmentservice {
 
    private userservice userservice;
 
    private string message = "hi!";
 
    @autowired
    public void setuserservice(userservice userservice) {
        this.userservice = userservice;
    }
 
    public string getmessage() {
        return message;
    }
}

同样,我们可以运行之前的测试,看看有没有异常抛出,程序结果是否是我们所期望的那样。

4.6 其他办法

我遇到的这个问题的情况,是 service 之间互相依赖导致了深层次的循环依赖。但是我真正想调用的是 mybatis plus 提供的基础方法,所以可以将依赖 service 换成依赖 mapper,因为 mapper 中也有这些基础方法。

@service
public class userservice {
    
    private final departmentmapper departmentmapper;
    @autowired
    public userserivce(departmentmapper departmentmapper) {
        this.departmentmapper = departmentmapper;
    }
    public list list() {
        retern departmentmapper.selectlist();
    }    
}

五、总结

‎在spring中,有很多方法可以处理循环依赖关系。首先要考虑的是重新设计你的bean,这样就不需要循环依赖关系:它们通常是可以改进的设计的症状。‎‎但是,如果您绝对需要在项目中具有循环依赖项,则可以按照此处建议的一些解决方法进行操作。

网站地图