Spring當中迴圈依賴很少有人講,今天一起來學習!
阿新 • • 發佈:2020-07-19
網上關於Spring迴圈依賴的部落格太多了,有很多都分析的很深入,寫的很用心,甚至還畫了時序圖、流程圖幫助讀者理解,我看了後,感覺自己是懂了,但是閉上眼睛,總覺得還沒有完全理解,總覺得還有一兩個坎過不去,對我這種有點笨的人來說,真的好難。當時,我就在想,如果哪一天,我理解了Spring迴圈依賴,一定要用自己的方式寫篇部落格,幫助大家更好的理解,等我理解後,一直在構思,到底怎麼應該寫,才能更通俗易懂,就在前幾天,我想通了,這麼寫應該更通俗易懂。在寫本篇部落格之前,我翻閱了好多關於Spring迴圈依賴的部落格,網上應該還沒有像我這樣講解的,現在就讓我們開始把。
## 什麼是迴圈依賴
一言以蔽之:兩者相互依賴。
在開發中,可能經常出現這種情況,只是我們平時並沒有注意到原來我們寫的兩個類、甚至多個類相互依賴了,為什麼注意不到呢?當然是因為沒有報錯,而且一點問題都木有,如果報錯了,或者產生了問題,我們還會注意不到嗎?這一切都是Spring的功勞,它在後面默默的為我們解決了迴圈依賴的問題。
如下所示:
```java
@Configuration
@ComponentScan
public class AppConfig {
}
```
```java
@Service
public class AuthorService {
@Autowired
BookService bookService;
}
```
```java
@Service
public class BookService {
@Autowired
AuthorService authorService;
}
```
```java
public class Main {
public static void main(String[] args) {
ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
BookService bookService = (BookService) annotationConfigApplicationContext.getBean("bookService");
System.out.println(bookService.authorService);
AuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean("authorService");
System.out.println(authorService.bookService);
}
}
```
執行結果:
```java
com.codebear.springcycle.AuthorService@63376bed
com.codebear.springcycle.BookService@4145bad8
```
可以看到BookService中需要AuthorService,AuthorService中需要BookService,類似於這樣的就叫迴圈依賴,但是神奇的是竟然一點問題沒有。
當然有些小夥伴可能get不到它的神奇之處,至於它的神奇之處在哪裡,我們放到後面再說。
## 任何迴圈依賴,Spring都能解決嗎
不行。
如果是原型 bean的迴圈依賴,Spring無法解決:
```java
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BookService {
@Autowired
AuthorService authorService;
}
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AuthorService {
@Autowired
BookService bookService;
}
```
啟動後,令人恐懼的紅色字型在控制檯出現了:
```java
image.png
```
如果是構造引數注入的迴圈依賴,Spring無法解決:
```java
@Service
public class AuthorService {
BookService bookService;
public AuthorService(BookService bookService) {
this.bookService = bookService;
}
}
```
```java
@Service
public class BookService {
AuthorService authorService;
public BookService(AuthorService authorService) {
this.authorService = authorService;
}
}
```
還是討厭的紅色字型:
```java
image.png
```
**迴圈依賴可以關閉嗎**
可以,Spring提供了這個功能,我們需要這麼寫:
```java
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setAllowCircularReferences(false);
applicationContext.register(AppConfig.class);
applicationContext.refresh();
}
}
```
再次執行,就報錯了:
```java
image.png
```
需要注意的是,我們不能這麼寫:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
applicationContext.setAllowCircularReferences(false);
如果你這麼寫,程式執行完第一行程式碼,整個Spring容器已經初始化完成了,你再設定不允許迴圈依賴,也於事無補了。
## 可以迴圈依賴的神奇之處在哪
有很多小夥伴可能並不覺得可以迴圈依賴有多麼神奇,那是因為不知道矛盾點在哪,接下來就來說說這個問題:
當beanA,beanB迴圈依賴:
建立beanA,發現依賴beanB;
建立beanB,發現依賴beanA;
建立beanA,發現依賴beanB;
建立beanB,發現依賴beanA。
...
好了,死迴圈了。
迴圈依賴的矛盾點就在於要建立beanA,它需要beanB,而建立beanB,又需要beanA,然後兩個bean都建立不出來。
## 如何簡單的解決迴圈依賴
如果你曾經看過Spring解決迴圈依賴的部落格,應該知道它其中有好幾個Map,一個Map放的是最完整的物件,稱為singletonObjects,一個Map放的是提前暴露出來的物件,稱為earlySingletonObjects。
在這裡,先要解釋下這兩個東西:
singletonObjects:單例池,其中存放的是經歷了Spring完整生命週期的bean,這裡面的bean的依賴都已經填充完畢了。
earlySingletonObjects:提前暴露出來的物件的map,其中存放的是剛剛創建出來的物件,沒有經歷Spring完整生命週期的bean,這裡面的bean的依賴還未填充完畢。
我們可以這麼做:
當我們建立完beanA,就把自己放到earlySingletonObjects,發現自己需要beanB,然後就去屁顛屁顛建立beanB;
當我們建立完beanB,就把自己放到earlySingletonObjects,發現自己需要beanA,然後就去屁顛屁顛建立beanA;
建立beanA前,先去earlySingletonObjects看一下,發現自己已經被創建出來了,把自己返回出去;
beanB拿到了beanA,beanB建立完畢,把自己放入singletonObjects;
beanA可以去singletonObjects拿到beanB了,beanA也建立完畢,把自己放到singletonObjects。
整個過程結束。
下面讓我們來實現這個功能:
首先,自定義一個註解,欄位上打上這個註解的,說明需要被Autowired:
```java
@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearAutowired {
}
```
再建立兩個迴圈依賴的類:
```java
public class OrderService {
@CodeBearAutowired
public UserService userService;
}
public class UserService {
@CodeBearAutowired
public OrderService orderService;
}
```
然後就是核心,建立物件,填充屬性,並解決Spring迴圈依賴的問題:
```java
public class Cycle {
// 單例池,裡面放的是完整的bean,已完成填充屬性
private f