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