SpringBoot字段注入和構(gòu)造函數(shù)注入的區(qū)別及說明
文章背景
在使用Spring開發(fā)項(xiàng)目時,我們經(jīng)常需要使用依賴注入來管理對象之間的依賴關(guān)系。Spring提供了多種依賴注入方式,如構(gòu)造函數(shù)注入、Setter方法注入和字段注入等。
這些方式各有優(yōu)缺點(diǎn),需要根據(jù)具體情況選擇合適的注入方式。
在本文中,我將分享我在開發(fā)過程中遇到的一些問題,以及我對這些問題的思考和解決方法。
主要涉及以下幾個方面:
- 字段注入和構(gòu)造函數(shù)注入的區(qū)別和聯(lián)系
- 為什么字段注入和Setter方法注入不會導(dǎo)致循環(huán)依賴的問題,而構(gòu)造函數(shù)注入會導(dǎo)致循環(huán)依賴的問題
- 為什么Spring不推薦使用字段注入,而推薦使用構(gòu)造函數(shù)注入
通過本文,我希望能夠幫助大家更好地理解Spring的依賴注入原理和實(shí)踐,以及如何避免一些常見的問題和錯誤。
什么是字段注入和構(gòu)造函數(shù)注入?
在SpringBoot中,我們可以使用@Autowired
注解來實(shí)現(xiàn)依賴注入,即讓Spring容器自動為我們的類提供所需的對象。
有三種常見的注入方式:字段注入,Setter
方法注入和構(gòu)造函數(shù)
注入。
- 字段注入:直接在類的屬性上使用
@Autowired
注解,無需編寫額外的代碼。 - Setter方法注入:在類的Setter方法上使用
@Autowired
注解,需要編寫相應(yīng)的Setter
方法。 - 構(gòu)造函數(shù)注入:在類的構(gòu)造函數(shù)上使用
@Autowired
注解,需要編寫相應(yīng)的構(gòu)造函數(shù)。
下面是一個簡單的例子,假設(shè)我們有一個UserService
接口和一個UserServiceImpl
實(shí)現(xiàn)類,以及一個UserController
類,我們想要在UserController
中使用UserService
對象。
// UserService接口 public interface UserService { void saveUser(User user); } // UserServiceImpl實(shí)現(xiàn)類 @Service public class UserServiceImpl implements UserService { @Override public void saveUser(User user) { // 保存用戶到數(shù)據(jù)庫 } } // UserController類 @Controller public class UserController { // 字段注入 @Autowired private UserService userService; // Setter方法注入 // private UserService userService; // @Autowired // public void setUserService(UserService userService) { // this.userService = userService; // } // 構(gòu)造函數(shù)注入 // private final UserService userService; // @Autowired // public UserController(UserService userService) { // this.userService = userService; // } public void createUser(User user) { userService.saveUser(user); // 其他邏輯 } }
這兩種方式有什么區(qū)別?
這兩種方式在功能上沒有區(qū)別,都可以實(shí)現(xiàn)依賴注入。但是在一些細(xì)節(jié)上有一些差異,主要有以下幾點(diǎn) :
- 可讀性:字段注入的代碼更簡潔,依賴項(xiàng)被隔離在一個地方,更容易閱讀。構(gòu)造函數(shù)注入的代碼更冗長,當(dāng)有多個依賴項(xiàng)時,構(gòu)造函數(shù)可能會變得臃腫。
- 不變性:構(gòu)造函數(shù)注入支持不變性,即可以將依賴項(xiàng)聲明為final類型,保證對象創(chuàng)建后不會被修改。這有利于線程安全性,狀態(tài)安全性和可讀性。字段注入不支持不變性,無法將依賴項(xiàng)聲明為final類型。
- 狀態(tài)安全性:構(gòu)造函數(shù)注入保證了對象被實(shí)例化為完整狀態(tài)或完全不被實(shí)例化。如果使用者使用new關(guān)鍵字創(chuàng)建對象,則必須提供所有依賴項(xiàng)作為參數(shù)。字段注入無法保證狀態(tài)安全性,如果使用者使用new關(guān)鍵字創(chuàng)建對象,則無法設(shè)置對象的狀態(tài)。唯一的選擇是使用反射設(shè)置私有字段。
- 循環(huán)依賴:循環(huán)依賴是指兩個或多個類相互依賴對方,導(dǎo)致無法正常創(chuàng)建對象。例如,如果A類依賴B類,B類依賴A類,則會產(chǎn)生循環(huán)依賴。循環(huán)依賴是一種不良的設(shè)計(jì)模式,應(yīng)該避免。
字段注入和Setter方法注入的聯(lián)系
字段注入和Setter方法注入都是通過反射來實(shí)現(xiàn)的,它們都可以在類的屬性上使用@Autowired
注解來標(biāo)注依賴關(guān)系。
它們的區(qū)別在于,字段注入是直接在屬性上使用@Autowired
注解,而Setter方法注入是在屬性對應(yīng)的Setter方法上使用@Autowired
注解。
字段注入和Setter方法注入的聯(lián)系有以下幾點(diǎn):
- 它們都是基于名稱或者類型來匹配依賴關(guān)系的。如果屬性名字或者Setter方法名字與Bean定義中的id或者name相同,則按照名稱匹配;否則按照屬性類型或者Setter方法參數(shù)類型匹配。
- 它們都不支持不變性,即無法將依賴項(xiàng)聲明為final類型。這可能會導(dǎo)致線程安全性,狀態(tài)安全性和可讀性的問題。
- 它們都可以避免循環(huán)依賴的問題,因?yàn)樗鼈兪窃趯ο髣?chuàng)建后才進(jìn)行依賴注入的,而不是在對象創(chuàng)建時。這樣可以避免構(gòu)造函數(shù)注入時可能出現(xiàn)的循環(huán)依賴異常。
為什么字段注入和Setter方法注入不會導(dǎo)致循環(huán)依賴的問題?
循環(huán)依賴是指兩個或多個類相互依賴對方,導(dǎo)致無法正常創(chuàng)建對象。例如,如果A類依賴B類,B類依賴A類,則會產(chǎn)生循環(huán)依賴。循環(huán)依賴是一種不良的設(shè)計(jì)模式,應(yīng)該避免。
在Spring中,循環(huán)依賴主要發(fā)生在構(gòu)造函數(shù)注入的情況下,因?yàn)闃?gòu)造函數(shù)注入是在對象創(chuàng)建時就進(jìn)行依賴注入的,而不是在對象創(chuàng)建后。這樣就會導(dǎo)致一個死鎖的情況,即A類要等待B類創(chuàng)建完成才能創(chuàng)建,而B類又要等待A類創(chuàng)建完成才能創(chuàng)建。
字段注入和Setter方法注入不會導(dǎo)致循環(huán)依賴的問題,因?yàn)樗鼈兪窃趯ο髣?chuàng)建后才進(jìn)行依賴注入的,而不是在對象創(chuàng)建時。這樣就可以避免死鎖的情況,即A類和B類都可以先創(chuàng)建出來,然后再互相注入對方。
Spring解決循環(huán)依賴的方法是通過提前暴露半成品對象(Early-Stage Object)來解決。半成品對象是指已經(jīng)實(shí)例化但還沒有完成初始化的對象。Spring會將半成品對象放入一個緩存中,當(dāng)其他對象需要依賴它時,就可以從緩存中獲取它,并進(jìn)行后續(xù)的屬性賦值和初始化操作。
簡述兩種方式的流程
字段注入和構(gòu)造函數(shù)注入的流程如下:
- 字段注入:當(dāng)IOC容器創(chuàng)建Bean時,它會先通過反射調(diào)用無參構(gòu)造函數(shù)來實(shí)例化對象,然后再通過反射獲取屬性上的@Autowired注解,并根據(jù)名稱或者類型來匹配依賴關(guān)系,最后通過反射將依賴關(guān)系注入到屬性中。
- 構(gòu)造函數(shù)注入:當(dāng)IOC容器創(chuàng)建Bean時,它會先通過反射獲取構(gòu)造函數(shù)上的@Autowired注解,并根據(jù)名稱或者類型來匹配依賴關(guān)系,然后再通過反射調(diào)用帶參構(gòu)造函數(shù)來實(shí)例化對象,并將依賴關(guān)系作為參數(shù)傳遞進(jìn)去。
為什么Spring不推薦使用字段注入?
Spring不推薦使用字段注入的原因有以下幾點(diǎn):
- 字段注入違反了單一職責(zé)原則,因?yàn)樗沟锰砑有碌囊蕾図?xiàng)非常容易,而不會引起警告。這可能導(dǎo)致類有太多的責(zé)任和關(guān)注點(diǎn),需要進(jìn)一步的檢查和重構(gòu)。
- 字段注入隱藏了依賴關(guān)系,因?yàn)樗鼪]有使用公共接口(方法或構(gòu)造函數(shù))來清楚地與依賴項(xiàng)通信。這樣就不利于類的可測試性和可重用性,也不利于依賴項(xiàng)的可選性和強(qiáng)制性的區(qū)分。
- 字段注入導(dǎo)致了依賴注入容器的耦合,因?yàn)樗沟妙悷o法脫離容器獨(dú)立運(yùn)行。這意味著類不能通過new關(guān)鍵字來創(chuàng)建,也不能切換到其他的依賴注入框架。
- 字段注入不支持不變性,因?yàn)樗鼰o法將依賴項(xiàng)聲明為final類型,也無法注入靜態(tài)變量。這可能會導(dǎo)致線程安全性,狀態(tài)安全性和可讀性的問題。
總結(jié)
字段注入和構(gòu)造函數(shù)注入都是Spring中常用的依賴注入方式,它們各有優(yōu)缺點(diǎn),開發(fā)人員應(yīng)根據(jù)具體情況選擇合適的注入方式。
一般來說,以下幾點(diǎn)可以作為參考:
- 如果依賴關(guān)系是必須的,且不需要重新配置或者重新注入,則推薦使用構(gòu)造函數(shù)注入,因?yàn)樗梢灾С植蛔冃院蜖顟B(tài)安全性。
- 如果依賴關(guān)系是可選的,或者需要重新配置或者重新注入,則推薦使用字段注入或者Setter方法注入,因?yàn)樗鼈兛梢蕴岣叽a的簡潔性和靈活性。
- 如果有循環(huán)依賴的問題,則不能使用構(gòu)造函數(shù)注入,只能使用字段注入或者Setter方法注入,因?yàn)樗鼈兛梢员苊馑梨i的情況。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)異步導(dǎo)出數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)異步導(dǎo)出數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-11-11Mybatis使用useGeneratedKeys獲取自增主鍵的方法
這篇文章主要給大家介紹了關(guān)于Mybatis使用useGeneratedKeys獲取自增主鍵的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Mybatis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡單定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡單定義與用法,簡要描述了循環(huán)隊(duì)列的概念、原理,并結(jié)合實(shí)例形式分析了java循環(huán)隊(duì)列的定義與使用方法,需要的朋友可以參考下2017-10-10Java圖形化界面設(shè)計(jì)之布局管理器之BorderLayout案例詳解
這篇文章主要介紹了Java圖形化界面設(shè)計(jì)之布局管理器之BorderLayout案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08WebSocket整合SSM(Spring,Struts2,Maven)的實(shí)現(xiàn)示例
這篇文章主要介紹了WebSocket整合SSM(Spring,Struts2,Maven)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01Java通過JsApi方式實(shí)現(xiàn)微信支付
本文講解了Java如何實(shí)現(xiàn)JsApi方式的微信支付,代碼內(nèi)容詳細(xì),文章思路清晰,需要的朋友可以參考下2015-07-07