一、什么是循環(huán)依賴呢?
類A依賴類B,類B也依賴類A,這種情況就會(huì)出現(xiàn)循環(huán)依賴。
Bean A → Bean B → Bean A
上面是比較容易發(fā)現(xiàn)的循環(huán)依賴,也有更深層次的循環(huán)依賴。
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
二、Spring 的循環(huán)依賴
當(dāng)Spring上下文在加載所有的bean時(shí),會(huì)嘗試按照他們他們關(guān)聯(lián)關(guān)系的順序進(jìn)行創(chuàng)建。如果不存在循環(huán)依賴時(shí),例如:
Bean A → Bean B → Bean C
Spring會(huì)先創(chuàng)建Bean C,再創(chuàng)建Bean B(并將Bean C注入到Bean B中),最后再創(chuàng)建Bean A(并將Bean B注入到Bean A中)。
但是,如果我們存在循環(huán)依賴,Spring上下文不知道應(yīng)該先創(chuàng)建哪個(gè)Bean,因?yàn)樗鼈円蕾囉诒舜?。在這種情況下,Spring會(huì)在加載上下文時(shí),拋出一個(gè)BeanCurrentlyInCreationException。
當(dāng)我們使用構(gòu)造方法進(jìn)行注入時(shí),會(huì)遇到這種情況。因?yàn)樗巧舷挛募虞d就被要求注入。
三、舉個(gè)栗子
用戶類需要調(diào)用組織類中的方法,于是通過(guò)構(gòu)造方法注入組織類。
@Service
public class UserService {
private final DepartmentService departmentSerivce;
/**
* 通過(guò)構(gòu)造方法注入DepartmentService類
*/
@Autowired
public UserSerivce(DepartmentService departmentService) {
this.departmentService = departmentService;
}
public List<Department> list() {
retern departmentService.list();
}
}
而組織類也剛好需要調(diào)用用戶類里的方法,于是它也通過(guò)構(gòu)造方法注入用戶類。
@Service
public class DepartmentService {
private final UserService userSerivce;
/**
* 通過(guò)構(gòu)造方法注入U(xiǎn)serService類
*/
@Autowired
public DepartmentSerivce(UserService userSerivce) {
this.userSerivce = userSerivce;
}
}
這種情況程序在編譯時(shí),就會(huì)報(bào)下面的錯(cuò)誤
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]
└─────┘
現(xiàn)在我們可以為測(cè)試編寫一個(gè)配置類,讓我們稱之為??TestConfig??,它指定要掃描組件的基本包。讓我們假設(shè)我們的bean在包"??com.baeldung.circulardependency??"中定義:?
@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}
最后,我們可以編寫一個(gè) JUnit 測(cè)試來(lái)檢查循環(huán)依賴關(guān)系。測(cè)試可以為空,因?yàn)樵谏舷挛募虞d期間將檢測(cè)到循環(huán)依賴關(guān)系。?
@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
}
}
如果您嘗試運(yùn)行此測(cè)試,您將得到以下異常:?
BeanCurrentlyInCreationException: Error creating bean with name 'UserService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
四、解決辦法
4.1 重新設(shè)計(jì)
當(dāng)出現(xiàn)這種循環(huán)依賴時(shí),很可能是在設(shè)計(jì)方面存在問題,沒有把每個(gè)類的職責(zé)很好的區(qū)分開。應(yīng)該嘗試正確重新設(shè)計(jì)組件,以便其層次結(jié)構(gòu)設(shè)計(jì)良好,并且不需要循環(huán)依賴項(xiàng)。?
如果無(wú)法重新設(shè)計(jì),那么可以考慮其他解決辦法。
4.2 使用 setter/field 方法注入
上面說(shuō)到,只有構(gòu)造方法是在上下文加載時(shí)就要求被注入,容易出現(xiàn)依賴循環(huán)。所以可以用其他的方式進(jìn)行依賴注入,setter 和 field 方法注入與構(gòu)造方法不同,它們不會(huì)在創(chuàng)Bean時(shí)就注入依賴,而是在被需要時(shí)才注入。
setter方法注入
@Service
public class UserService {
private DepartmentService departmentSerivce;
@Autowired
public void setDepartmentSerivce(DepartmentService departmentService) {
this.departmentService = departmentService;
}
public List<Department> list() {
retern departmentService.list();
}
}
另一個(gè)類也是同理使用setter方法注入
field方法注入
(使用@Autowired)
@Service
public class UserService {
@Autowired
private DepartmentService departmentSerivce;
public List<Department> list() {
retern departmentService.list();
}
}
4.3 使用 @Lazy
解決Spring 循環(huán)依賴的一個(gè)簡(jiǎn)單方法就是對(duì)一個(gè)Bean使用延時(shí)加載。也就是說(shuō):這個(gè)Bean并沒有完全的初始化完,實(shí)際上他注入的是一個(gè)代理,只有當(dāng)他首次被使用的時(shí)候才會(huì)被完全的初始化。
我們對(duì) UserService 進(jìn)行修改,結(jié)果如下:
@Service
public class UserService {
private DepartmentService departmentSerivce;
@Autowired
public UserSerivce(@Lazy DepartmentService departmentService) {
this.departmentService = departmentService;
}
public List<Department> list() {
retern departmentService.list();
}
}
如果你現(xiàn)在運(yùn)行測(cè)試,你會(huì)發(fā)現(xiàn)之前的錯(cuò)誤不存在了。
4.4 使用 @PostConstruct
打破循環(huán)的另一種方式是,在要注入的屬性(該屬性是一個(gè)bean)上使用 @Autowired,并使用@PostConstruct 標(biāo)注在另一個(gè)方法,且該方法里設(shè)置對(duì)其他的依賴。
我們的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;
}
}
現(xiàn)在我們運(yùn)行我們修改后的代碼,發(fā)現(xiàn)并沒有拋出異常,并且依賴正確注入進(jìn)來(lái)。
4.5 實(shí)現(xiàn)ApplicationContextAware and InitializingBean接口
如果一個(gè)Bean實(shí)現(xiàn)了ApplicationContextAware,該Bean可以訪問Spring上下文,并可以從那里獲取到其他的bean。實(shí)現(xiàn)InitializingBean接口,表明這個(gè)bean在所有的屬性設(shè)置完后做一些后置處理操作(調(diào)用的順序?yàn)閕nit-method后調(diào)用);在這種情況下,我們需要手動(dòng)設(shè)置依賴。
@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;
}
}
同樣,我們可以運(yùn)行之前的測(cè)試,看看有沒有異常拋出,程序結(jié)果是否是我們所期望的那樣。
4.6 其他辦法
我遇到的這個(gè)問題的情況,是 Service 之間互相依賴導(dǎo)致了深層次的循環(huán)依賴。但是我真正想調(diào)用的是 Mybatis Plus 提供的基礎(chǔ)方法,所以可以將依賴 Service 換成依賴 Mapper,因?yàn)?Mapper 中也有這些基礎(chǔ)方法。
@Service
public class UserService {
private final DepartmentMapper departmentMapper;
@Autowired
public UserSerivce(DepartmentMapper departmentMapper) {
this.departmentMapper = departmentMapper;
}
public List<Department> list() {
retern departmentMapper.selectList();
}
}
五、總結(jié)
?在Spring中,有很多方法可以處理循環(huán)依賴關(guān)系。首先要考慮的是重新設(shè)計(jì)你的bean,這樣就不需要循環(huán)依賴關(guān)系:它們通常是可以改進(jìn)的設(shè)計(jì)的癥狀。??但是,如果您絕對(duì)需要在項(xiàng)目中具有循環(huán)依賴項(xiàng),則可以按照此處建議的一些解決方法進(jìn)行操作。