說明
-
循環依賴是一個大家討論很多的話題,它更多是一個工程上的問題而不是技術問題,我們需要首先有一定的認知:
- 如同兩個人相互幫忙,兩個類之間你調用我的,我調用你的是很正常也很自然的需求模型。
- 單一依賴確實有好處,改動一個最頂層類時不需要在意對底部類的影響,但是從本來就自然的模型非要理順的話就需要額外付出代價,例如額外的拆分類。
-
循環依賴可以分這幾種:
- 從小的來說是類之間的相互引用。
- 再大一點的來說是同一個項目下不同模塊之間的引用。
- 再再大一點的來說涉及到微服務或不同類庫之間引用。
對於微服務級別或是模塊級別的引用來說解決循環依賴是有必要的,因爲這可能牽扯到不同人分工協作的問題,而類之間尤其是同一模塊下的類之間是否禁止循環依賴實際上是有爭議的,本文只討論同一模塊下的類之間的循環引用。
-
一些解決循環依賴的方法類似
@Lazy
、getBean
等實際上解決的不是循環依賴,而是解決的springboot
啓動時的循環依賴檢測,但本質上它們還是相互引用,所以這裏不討論這些方法,只討論拆分類的方法。 -
個人目前比較認同的是同一個人寫的同一個模塊下的功能是可以循環依賴的,增加額外的拆分類反而會增加複雜度及影響效率,但
springboot 2.6
版本之後默認禁止了循環依賴,所以個人也在思考,如果想要拆分的化要怎麼拆分,目前總結了如下兩種不同類型的循環引用拆分示例。
示例
情形一
-
最常見的
老師
、學生
這種或是主表
、子表
相互關聯的:@Component public class Teacher { @Autowired private Student student; public void method() { //獲取某教師下學生類別 List<String> students = student.getStudentsByTeacher("xxx"); System.out.println(students); } }
@Component public class Student { @Autowired private Teacher teacher; public void method() { //獲取學生歸屬的教師 String teacherStr = teacher.getTeacherByStudent("xxx"); System.out.println(teacherStr); } }
-
這種拆分比較簡單,類似數據庫多對多的中間表,我們也創建一箇中間類,然後
Teacher
和Student
類不要依賴彼此,直接抽取方法到中間類中或是都引用中間類:@Component public class TeacherStudent { @Autowired private Teacher teacher; @Autowired private Student student; public void method1() { //獲取某教師下學生類別 List<String> students = student.getStudentsByTeacher("xxx"); System.out.println(students); } public void method2() { //獲取學生歸屬的教師 String teacherStr = teacher.getTeacherByStudent("xxx"); System.out.println(teacherStr); } }
情形二
-
另一種常用的場景是引用第三方類庫
A
,然後在配置類B
中用@Bean
來實例化,而類A
是通過讀取數據庫中的配置(通過類C
)來組裝參數,而當數據庫配置變更時(類C
中更新),由於參數變化同時也要重置類A
實例,我們在集成微信、釘釘等SDK時會經常遇到此情況,如果直接按照此邏輯寫的話,就是下述的代碼://B本身是個配置類 @Configuration class B { @Autowired private C c; @Bean public A init(){ A a = new A(); //引用c的數據庫中數據來組裝成A實例 a.setProp(c.getProp()); return a; } }
@Component class C { @Autowired private A a; public void update(){ //修改數據庫相關後,又來重置A實例 a.reset(); } }
-
此情況下最主要的耦合就是
類A
需要類C
的數據來作爲配置項,所以把這個耦合獨立出來,而類B
中去除類C
的引用,僅僅是生成類A
的bean
://用@PostConstruct @Component public class D { @Autowired private A a; @Autowired private C c; @PostConstruct public void init(){ a.setProp(c.getProp()); } } //或是@Autowired註解到方法上 @Component public class D { @Autowired public void init(A a, C c) { a.setProp(c.getProp()); } }
-
雖然從需求上
類A
依賴類C
,但本質上類A
並不需要依賴任何類,和第一種情況不同的是類A
是一個第三方的類庫,我們無法修改其引用及方法,而其本身並不是個bean
,需要我們額外去操作。
結果
- 上述的情況都是額外增加一個拆分類來處理,這樣無形中增加了代碼量,尤其是第一種情形太常見了,除非是項目初始時就規定好禁止
service
層互相調用,而是單獨再劃分一層來處理(類似阿里的manager
層),否則的話個人寧願用@Lazy
來解決掉循環依賴的報錯。 - 解決循環依賴上述同模塊內的相對簡單些,只是增加代碼量而已,當涉及到模塊或微服務時,則完全不一樣,要考慮業務邏輯及架構等一系列問題,感覺很是麻煩。
- 以上只是個人見解,有更好的觀點可發到評論區一起討論下。