通常场景是一个Bean A依赖Bean B,而Bean B也依赖Bean A.
Bean A → Bean B → Bean Ahtml
固然咱们也能够添加更多的依赖层次,好比:
Bean A → Bean B → Bean C → Bean D → Bean E → Bean Aspring
当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。框架
当咱们使用构造方法进行注入时,也会遇到这种状况。若是您使用其它类型的注入,你应该不会遇到这个问题。由于它是在须要时才会被注入,而不是上下文加载被要求注入。ide
咱们定义两个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; } }
如今,咱们写一个测试配置类,姑且称之为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 } }
若是您运行这个测试,你会获得如下异常:this
BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?
咱们将使用一些最流行的方式来处理这个问题。设计
4.1 从新设计代理
当你有一个循环依赖,极可能你有一个设计问题而且各责任没有获得很好的分离。你应该尽可能正确地从新设计组件,以便它们的层次是精心设计的,也没有必要循环依赖。
若是不能从新设计组件(可能有不少的缘由:遗留代码,已经被测试并不能修改代码,没有足够的时间或资源来彻底从新设计......),但有一些变通方法来解决这个问题。
4.2 使用 @Lazy
解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并无彻底的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被彻底的初始化。
咱们对CircularDependencyA 进行修改,结果以下:
@Component public class CircularDependencyA { private CircularDependencyB circB; @Autowired public CircularDependencyA(@Lazy CircularDependencyB circB) { this.circB = circB; } }
若是你如今运行测试,你会发现以前的错误不存在了。
4.3 使用 Setter/Field 注入
其中最流行的解决方法,就是Spring文档中建议,使用setter注入。
简单地说,你对你需要注入的bean是使用setter注入(或字段注入),而不是构造函数注入。经过这种方式建立Bean,实际上它此时的依赖并无被注入,只有在你需要的时候他才会被注入进来。
让咱们开始动手干吧。咱们将在CircularDependencyB 中添加另外一个属性,并将咱们两个Class Bean从构造方法注入改成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()); } }
下面对上面看到的注解进行说明:
@Bean:在Spring框架中,标志着他被建立一个Bean并交给Spring管理
@Test:测试将获得从Spring上下文中获取CircularDependencyA bean并断言CircularDependencyB已被正确注入,并检查该属性的值。
4.4 使用 @PostConstruct
打破循环的另外一种方式是,在要注入的属性(该属性是一个bean)上使用 @Autowired ,并使用@PostConstruct 标注在另外一个方法,且该方法里设置对其余的依赖。
咱们的Bean将修改为下面的代码:
@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 and InitializingBean接口
若是一个Bean实现了ApplicationContextAware,该Bean能够访问Spring上下文,并能够从那里获取到其余的bean。实现InitializingBean接口,代表这个bean在全部的属性设置完后作一些后置处理操做(调用的顺序为init-method后调用);在这种状况下,咱们须要手动设置依赖。
@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; } }
public class CircularDependencyB { private CircularDependencyA circA; private String message = "Hi!"; @Autowired public void setCircA(CircularDependencyA circA) { this.circA = circA; } public String getMessage() { return message; } }
一样,咱们能够运行以前的测试,看看有没有异常抛出,程序结果是不是咱们所指望的那样。
有不少种方法来应对Spring的循环依赖。但考虑的第一件事就是从新设计你的bean,因此没有必要循环依赖:他们一般是能够提升设计的一种症状。 可是,若是你在你的项目中确实是须要有循环依赖,那么你能够遵循一些这里提出的解决方法。