1. 循环依赖是什么?
bean a 依赖 b,bean b 依赖 a这种情况下出现循环依赖。
bean a → bean b → bean a
更复杂的间接依赖造成的循环依赖如下。
bean a → bean b → bean c → bean d → bean e → bean a
2. 循环依赖会产生什么结果?
当spring正在加载所有bean时,spring尝试以能正常创建bean的顺序去创建bean。
例如,有如下依赖:
bean a → bean b → bean c
spring先创建beanc,接着创建bean b(将c注入b中),最后创建bean a(将b注入a中)。
但当存在循环依赖时,spring将无法决定先创建哪个bean。这种情况下,spring将产生异常beancurrentlyincreationexception。
当使用构造器注入时经常会发生循环依赖问题。如果使用其它类型的注入方式能够避免这种问题。
3. 构造器注入循环依赖实例
首先定义两个相互通过构造器注入依赖的bean。
@component public class circulardependencya { private circulardependencyb circb; @autowired public circulardependencya(circulardependencyb circb) { this.circb = circb; } }
@component public class circulardependencyb { private circulardependencya circa; @autowired public circulardependencyb(circulardependencya circa) { this.circa = circa; } }
@configuration @componentscan(basepackages = { "com.baeldung.circulardependency" }) public class testconfig { }
@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 } }
运行方法givencirculardependency_whenconstructorinjection_thenitfails将会产生异常:
beancurrentlyincreationexception: error creating bean with name ‘circulardependencya': requested bean is currently in creation: is there an unresolvable circular reference?
4.解决方法
处理这种问题目前有如下几种常见方式。
4.1 重新设计
重新设计结构,消除循环依赖。
4.2 使用注解 @lazy
一种最简单的消除循环依赖的方式是通过延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象完成注入。
@component public class circulardependencya { private circulardependencyb circb; @autowired public circulardependencya(@lazy circulardependencyb circb) { this.circb = circb; } }
使用@lazy后,运行代码,可以看到异常消除。
4.3 使用setter/field注入
spring文档建议的一种方式是使用setter注入。当依赖最终被使用时才进行注入。对前文的样例代码少做修改,来观察测试效果。
@component public class circulardependencya { private circulardependencyb circb; @autowired public void setcircb(circulardependencyb circb) { this.circb = circb; } public circulardependencyb getcircb() { return circb; } }
@component public class circulardependencyb { private circulardependencya circa; private string message = "hi!"; @autowired public void setcirca(circulardependencya circa) { this.circa = circa; } public string getmessage() { return message; } }
@runwith(springjunit4classrunner.class) @contextconfiguration(classes = { testconfig.class }) public class circulardependencytest { @autowired applicationcontext context; @bean public circulardependencya getcirculardependencya() { return new circulardependencya(); } @bean public circulardependencyb getcirculardependencyb() { return new circulardependencyb(); } @test public void givencirculardependency_whensetterinjection_thenitworks() { circulardependencya circa = context.getbean(circulardependencya.class); assert.assertequals("hi!", circa.getcircb().getmessage()); } }
4.4 使用@postconstruct
@component public class circulardependencya { @autowired private circulardependencyb circb; @postconstruct public void init() { circb.setcirca(this); } public circulardependencyb getcircb() { return circb; } }
@component public class circulardependencyb { private circulardependencya circa; private string message = "hi!"; public void setcirca(circulardependencya circa) { this.circa = circa; } public string getmessage() { return message; }
4.5 实现applicationcontextaware与initializingbean
@component public class circulardependencya implements applicationcontextaware, initializingbean { private circulardependencyb circb; private applicationcontext context; public circulardependencyb getcircb() { return circb; } @override public void afterpropertiesset() throws exception { circb = context.getbean(circulardependencyb.class); } @override public void setapplicationcontext(final applicationcontext ctx) throws beansexception { context = ctx; } }
@component public class circulardependencyb { private circulardependencya circa; private string message = "hi!"; @autowired public void setcirca(circulardependencya circa) { this.circa = circa; } public string getmessage() { return message; } }
5.总结
处理循环依赖有多种方式。首先考虑是否能够通过重新设计依赖来避免循环依赖。如果确实需要循环依赖,那么可以通过前文提到的方式来处理。优先建议使用setter注入来解决。