基于spring DI的三種注入方式分析
一.前言: IOC(控制反轉(zhuǎn))與DI(依賴注入)
Spring框架對Java開發(fā)的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反轉(zhuǎn))和AOP,平時使用最多的就是其中的IOC,我們通過將組件交由Spring的IOC容器管理,將對象的依賴關(guān)系由Spring控制,避免硬編碼所造成的過度程序耦合。
在講依賴注入之前,我覺得有必要了解一下IOC(控制反轉(zhuǎn))與DI(依賴注入)的關(guān)系,在這篇文章中有詳細(xì)的介紹:spring IOC 與 DI。
二.DI的三種常見注入方式
DI的三種常見注入方式為:setter注入、構(gòu)造器注入和基于注解的注入(也叫field注入),下面來分別講講他們的特點(diǎn)。
2.1 基于注解注入
首先來看一下它的實(shí)現(xiàn):
@RestController @RequestMapping("/annotation") public class AnnotationController { @Autowired private DiService diService; @GetMapping("/test001") public String test001() { return diService.test001("annotation"); } }
這種方式應(yīng)該是目前最常見的注入方式了,原因很簡單:
1、注入方式非常簡單:加上@Autowired注解,加入要注入的字段,即可完成。
2、使得整體代碼簡潔明了,看起來美觀大方。
在介紹注解注入的方式前,先簡單了解bean的一個屬性autowire,autowire主要有三個屬性值:constructor,byName,byType。
- constructor:通過構(gòu)造方法進(jìn)行自動注入,spring會匹配與構(gòu)造方法參數(shù)類型一致的bean進(jìn)行注入,如果有一個多參數(shù)的構(gòu)造方法,一個只有一個參數(shù)的構(gòu)造方法,在容器中查找到多個匹配多參數(shù)構(gòu)造方法的bean,那么spring會優(yōu)先將bean注入到多參數(shù)的構(gòu)造方法中。
- byName:被注入bean的id名必須與set方法后半截匹配,并且id名稱的第一個單詞首字母必須小寫,這一點(diǎn)與手動set注入有點(diǎn)不同。
- byType:查找所有的set方法,將符合符合參數(shù)類型的bean注入。
下面進(jìn)入正題:
注解方式注冊bean:
在以前的開發(fā)中,我們主要使用四種注解注冊bean,每種注解可以任意使用,只是語義上有所差異:
- @Component:可以用于注冊所有bean
- @Repository:主要用于注冊dao層的bean
- @Controller:主要用于注冊控制層的bean
- @Service:主要用于注冊服務(wù)層的bean
隨著springboot的流行,@Bean注解也逐漸的被我們使用起來。Spring的@Bean注解用于告訴方法,產(chǎn)生一個Bean對象,然后這個Bean對象交給Spring管理。產(chǎn)生這個Bean對象的方法Spring只會調(diào)用一次,隨后這個Spring將會將這個Bean對象放在自己的IOC容器中。
注解方式注入依賴(主要有兩種):
- @Resource :java的注解,默認(rèn)以byName的方式去匹配與屬性名相同的bean的id,如果沒有找到就會以byType的方式查找,如果byType查找到多個的話,使用@Qualifier注解(spring注解)指定某個具體名稱的bean。
- @Autowired :spring注解,默認(rèn)是以byType的方式去匹配類型相同的bean,可以結(jié)合@Qualifier 注解根據(jù)byName方式匹配。
關(guān)于他們的具體用法與區(qū)別,因?yàn)閮?nèi)容比較多,所以寫在另一篇博客中,請見:@Autowired 和 @Resource 詳解
2.2 構(gòu)造器注入
老規(guī)矩,先上代碼示例:
@RestController @RequestMapping("/constructor") public class ConstructorController { private final DiService diService; private final String result; public ConstructorController(DiService diService) { this.diService = diService; this.result = diService.test001("constructor"); } @GetMapping("/test001") public String test001() { return diService.test001(this.result); } }
這里有一個問題,如果只有一個有參數(shù)的構(gòu)造方法并且參數(shù)類型與注入的bean的類型匹配,那就會注入到該構(gòu)造方法中。如果有多個有參數(shù)的構(gòu)造方法并且每個構(gòu)造方法的參數(shù)列表里面都有要注入的屬性,那userDaoJdbc會注入到哪里呢?
在Spring4.x版本中推薦的注入方式就是這種,相較于上面的field注入方式而言,就顯得有點(diǎn)難看,特別是當(dāng)注入的依賴很多(5個以上)的時候,就會明顯的發(fā)現(xiàn)代碼顯得很臃腫。對于從field注入轉(zhuǎn)過來+有強(qiáng)迫癥的同學(xué)來說,簡直可以說是石樂志 ,但是為啥spring官方還會這么推薦呢?
官方文檔里是這么說的:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
翻譯一下就是:Spring團(tuán)隊(duì)通常提倡構(gòu)造函數(shù)注入,因?yàn)樗试S將應(yīng)用程序組件實(shí)現(xiàn)為不可變的對象,并確保所需的依賴不為空。此外,注入構(gòu)造函數(shù)的組件總是以完全初始化的狀態(tài)返回給客戶機(jī)(調(diào)用)代碼。
簡單解釋一下:
- 不可變的對象:其實(shí)說的就是final關(guān)鍵字,這里不再多解釋了。
- 依賴不為空:省去了我們對其檢查。當(dāng)要實(shí)例化ConstructorController的時候,由于自己實(shí)現(xiàn)了有參數(shù)的構(gòu)造函數(shù),所以不會調(diào)用默認(rèn)構(gòu)造函數(shù),那么就需要Spring容器傳入所需要的參數(shù),所以就兩種情況:1、有該類型的參數(shù)->傳入,OK 。2:無該類型的參數(shù)->報(bào)錯。這樣就可以保證不會為空。
- 完全初始化的狀態(tài):這個可以跟上面的依賴不為空結(jié)合起來,向構(gòu)造器傳參之前,要確保注入的內(nèi)容不為空,那么肯定要調(diào)用依賴組件的構(gòu)造方法完成實(shí)例化。而在Java類加載實(shí)例化的過程中,構(gòu)造方法是最后一步(之前如果有父類先初始化父類,然后自己的成員變量,最后才是構(gòu)造方法,這里不詳細(xì)展開。)。所以返回來的都是初始化之后的狀態(tài)。
與注解方式注入相比,構(gòu)造器注入可復(fù)用性高,如果使用field注入,缺點(diǎn)顯而易見,對于IOC容器以外的環(huán)境,除了使用反射來提供它需要的依賴之外,無法復(fù)用該實(shí)現(xiàn)類。而且將一直是個潛在的隱患,因?yàn)槟悴徽{(diào)用將一直無法發(fā)現(xiàn)NPE的存在。
相對于注解注入,構(gòu)造器注入可以防止循環(huán)依賴的問題,若如下代碼:
public class A { @Autowired private B b; } public class B { @Autowired private A a; }
如果使用構(gòu)造器注入,在spring項(xiàng)目啟動的時候,就會拋出:
BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?
從而提醒你避免循環(huán)依賴,如果是注解注入的話,啟動的時候不會報(bào)錯,在使用那個bean的時候才會報(bào)錯。
2.3 setter注入
這是在spring3.x出來的時候,官方推薦的注入方式,但是在spring4.x以后就沒有見它推薦了,而且在實(shí)際開發(fā)中已經(jīng)很少能見到這種注入方式了。
下面來看一下它的實(shí)現(xiàn):
@RestController @RequestMapping("/setter") public class SetterController { private DiService diService; @Autowired public void setDiService(DiService diService) { this.diService = diService; } @GetMapping("/test001") public String test001() { return diService.test001("setter"); } }
試想一下,一旦需要注入的組件很多,那我們會累死的,所以大家都不喜歡用它也是情理之中的事情。
這里有一點(diǎn)需要注意:如果通過set方法注入屬性,那么spring會通過默認(rèn)的空參構(gòu)造方法來實(shí)例化對象,所以如果在類中寫了一個帶有參數(shù)的構(gòu)造方法,一定要把空參數(shù)的構(gòu)造方法寫上,否則spring沒有辦法實(shí)例化對象,導(dǎo)致報(bào)錯。
總結(jié)
這么多的依賴注入方式,我們應(yīng)該怎么選擇呢?那種方式最好呢?
其實(shí),有句古話說的很對,合適自己的才是最好的,我們需要看情況來選擇使用哪種注入方式。
使用構(gòu)造器注入的好處:
- 保證依賴不可變(final關(guān)鍵字)
- 保證依賴不為空(省去了我們對其檢查)
- 保證返回客戶端(調(diào)用)的代碼的時候是完全初始化的狀態(tài)
- 避免了循環(huán)依賴
- 提升了代碼的可復(fù)用性
另外,當(dāng)有一個依賴有多個實(shí)現(xiàn)的使用,推薦使用注解方式注入的方式來指定注入的類型或name,使用setter注入指定類型。這是spring官方博客對setter注入方式和構(gòu)造器注入的比較。
謝謝大家看完了,以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。如果有描述不對的地方歡迎指正,與大家共同進(jìn)步!
相關(guān)文章
SpringBoot使用Spring Security實(shí)現(xiàn)登錄注銷功能
這篇文章主要介紹了SpringBoot使用Spring Security實(shí)現(xiàn)登錄注銷功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-09-09springboot使用JdbcTemplate完成對數(shù)據(jù)庫的增刪改查功能
這篇文章主要介紹了springboot使用JdbcTemplate完成對數(shù)據(jù)庫的增刪改查功能,需要的朋友可以參考下2017-12-12java servlet手機(jī)app訪問接口(一)數(shù)據(jù)加密傳輸驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了java servlet手機(jī)app訪問接口(一),數(shù)據(jù)加密傳輸驗(yàn)證,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12一篇文章教你如何用Java自定義一個參數(shù)校驗(yàn)器
這篇文章主要介紹了使用java自定義一個參數(shù)校驗(yàn)器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)2021-09-09