Spring依賴注入的三種方式(好的 壞的和丑的)
關于spring bean三種注入方式的優缺點對比,翻譯自Spring DI Patterns: The Good, The Bad, and The Ugly,水平有限,如有錯誤請指正。
Spring開發者會很熟悉spring強大的依賴注入API,這些API可以讓你用@Bean的注解讓Spring實例化和管理Bean。Bean之間的任何依賴都會被spring解析和注入。
三種依賴于注解的注入方法
spring有三種注解的方式讓你來聲明類的依賴。
字段注入(壞的)
import org.springframework.beans.factory.annotation.Autowired; public class MyBean { @Autowired private AnotherBean anotherBean; //Business logic... }
1
2
3
4
5
6
設值注入(丑的)
import org.springframework.beans.factory.annotation.Autowired; public class MyBean { private AnotherBean anotherBean; @Autowired public void setAnotherBean(final AnotherBean anotherBean) { this.anotherBean = anotherBean; } //Business logic... }
1
2
3
4
5
6
7
8
9
構造器注入(好的)
public class MyBean { private final AnotherBean anotherBean; public MyBean(final AnotherBean anotherBean) { this.anotherBean = anotherBean; } //Business logic... }
1
2
3
4
5
6
7
字段注入難以忽視的真相
這幾種方式中最常用的就是字段注入,很有可能是因為這是最方便的方式。不幸的是,因為它的普遍性,開發者很少了解到其他兩種方式相互之間的優缺點。
使用字段注入的類會變得越來越難以維護
當你用的字段注入模式,并且想在類里增加依賴時,你只需要加一個字段,然后加上@Autowired或者@Inject注解,然后就可以走了。聽起來很棒,但幾個月以后,你的類就會變成只有上帝才能理清楚的類了。 當然,這也很可能發生在另外兩中方式上,但是另兩種方式能迫使你更關注類中的依賴關系。
只要你用了字段注入,單測就沒法做了
當我看了Josh Long關于Spring boot的演講后,這句話就一直縈繞在我的腦海里, 從某種意義上來說,它也促使我寫下這篇文章。你怎么測試字段注入的類?很有可能你正在回想那些不太直觀的 Mockito 用法,就像這樣。
import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class MyBeanTest { @Mock private AnotherBean anotherBean; @InjectMocks private MyBean target; //Tests... }
1
2
3
4
5
6
7
8
9
10
11
12
這種利用反射的方式迫使開發者需要關注很多其他的地方,比如
如果MyBean有多個其他依賴怎么辦?
我是否應該創建一個target實例,或者只是聲明它?有什么不同?
當依賴用到泛型的時候你是否能保證類型安全?
如果你只需要部分依賴的真實實現怎么辦?
用了字段注入的類都是非final的,容易產生循環依賴
如果是你想把@Autowired自動注入的字段聲明為final類型的,編譯器會直接報錯,是不是很煩人。 而且這個字段只能被設置一次。除非你加了@Lazy注解,否則spring會在啟動的時候去解析依賴圖,你的bean可能因為循環依賴報出一個BeanCurrentlyInCreationException,例如:
public class A { @Autowired private B b; } public class B { @Autowired private C c; } public class C { @Autowired private A a; }
1
2
3
4
5
6
7
8
9
10
11
12
現實中肯定不會出現這么簡單的錯誤,但實際中可能會出現很多因為繼承、跨類庫,跨架構導致的依賴迷宮。這個問題可以提供把其中某個字段聲明為非必須(可以通過@Autowired(required = false)允許為空),或者使用懶加載(使用@Lazy可以再解析完bean之后再設值)。遇到過這個Exception的人都知道,找到循環依賴中確切的一環是非常耗時耗力的工作。一旦你找到了,你如何確定犧牲那個依賴呢?你怎么恰當的把這些寫到文檔里呢?
spring中有很多種解決循環依賴的方法,而且現在有些方法開始變的很惡心了。
最簡潔
很多java開發者都喜歡這種方式
便利會弱化代碼結構設計
很難測試
依賴不能是可變的(無法final)
容易出現循環依賴
需要使用到多個spring或者java注解
設值注入
模板和封裝
三種方式里,設值注入是最模板化的,每個bean都必須有有個setter函數,每個setter函數必須加@Autowired或@Inject注解。這種方式你不用考慮你類依賴的數量問題,這算是另一種設計方式。 但你過多暴露類的內部,違反了開放封閉原則。
設值注入讓單測變的簡單
不需要反射的黑魔法,你只需要把你的依賴set進去。
import org.junit.Before; import org.mockito.Mockito; public class MyBeanTest { private MyBean target = new MyBean(); private AnotherBean anotherBean = Mockito.mock(AnotherBean.class); @Before public void setUp() { myBean.setAnotherBean(anotherBean); } //Tests... }
1
2
3
4
5
6
7
8
9
10
11
設值注入對循環依賴免疫
使用設值注入,spring不會對你的bean做有向無環圖依賴分析,這就意味著可以有循環依賴。允許循環依賴是把雙刃劍,你不必處理那些因為循環依賴導致的惡心的問題,但你的代碼以后也就很難分解開了。 試試上BeanCurrentlyInCreationException只是在啟動時告訴你你的設計有問題。
對循環依賴免疫
隨著setter的添加,高度耦合的類很容易被識別出來。
違反開放封閉原則
會把循環依賴隱藏掉
三種方法里最模板化的方式
依賴不能是可變的(無法final)
終結方案:構造器注入
事實證明構造器注入是最佳的依賴注入解決方案。一些新的支持持續集成的平臺,比如Angular,已經從其他平臺吸取了教訓,只支持構造器注入。
構造器注入能暴露出過度耦合的問題
無論什么時候你的類需要一個新的依賴,你都得加一個構造參數,這就會強迫你去審視你類的耦合度。我發現少于3個依賴是比較好的,如果多于5個依賴,就應該重構了。只在短短幾行連續的代碼上數有多少個依賴是很容易的。
額外的好處是,由于final字段可以在構造函數中初始化,所以我們的依賴關系可以是final的。恩,就應該是這樣!
測試注入的構造函數類很簡單
甚至比設值注入更簡單。
import org.mockito.Mockito; public class MyBeanTest { private AnotherBean anotherBean = Mockito.mock(AnotherBean.class); private MyBean target = new MyBean(anotherBean); //Tests... }
1
2
3
4
5
6
注入子類的構造函數必須具有非默認構造函數
使用構造函數注入的類的任何子類都必須具有調用父構造函數的構造函數。如果您繼承了Spring組件,這就很麻煩了。我個人很少碰到這種情況。我盡量避免在父組件中注入依賴——我通常是通過組合而不是繼承完成的。
依賴可以是final的
spring官方推薦的方式
三種方式里最容易測試的方式
高耦合類隨著構造參數的增長很容易被識別出來
其他開發平臺的開發者也很熟悉
不需要依賴@Autowired注解
構造函數需要下沉到子類
容易產生循環依賴
結論
有時候其他模式也有意義,但“為了與代碼庫的其余部分保持一致”和“使用字段注入模式更簡單”并不是有效的借口。
例如,使用設值注入模式從xml setter注入方式遷移,或者需要修復BeanCurrentlyInCreationException問題時的中間狀態,但并不意味著你最終就應該是這樣。
甚至字段注入模式也足夠了,例如,設計解決方案或回答StackOverflow上的問題時,除非他們的問題是關于Java中的依賴注入。在這種情況下,您應該用字段注入方便說明問題。
Java Spring
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。