spring DI的三种注入方式分析
一.前言: IOC(控制反轉)與DI(依賴注入)
Spring框架對Java開發的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反轉)和AOP,平時使用最多的就是其中的IOC,我們通過將組件交由Spring的IOC容器管理,將對象的依賴關系由Spring控制,避免硬編碼所造成的過度程序耦合。
在講依賴注入之前,我覺得有必要了解一下IOC(控制反轉)與DI(依賴注入)的關系,在這篇文章中有詳細的介紹:spring IOC 與 DI。
二.DI的三種常見注入方式
DI的三種常見注入方式為:setter注入、構造器注入和基于注解的注入(也叫field注入),下面來分別講講他們的特點。
2.1 基于注解注入
首先來看一下它的實現:
@RestController @RequestMapping("/annotation") public class AnnotationController {@Autowiredprivate DiService diService;@GetMapping("/test001")public String test001() {return diService.test001("annotation");} }這種方式應該是目前最常見的注入方式了,原因很簡單:
在介紹注解注入的方式前,先簡單了解bean的一個屬性autowire,autowire主要有三個屬性值:constructor,byName,byType。
- constructor:通過構造方法進行自動注入,spring會匹配與構造方法參數類型一致的bean進行注入,如果有一個多參數的構造方法,一個只有一個參數的構造方法,在容器中查找到多個匹配多參數構造方法的bean,那么spring會優先將bean注入到多參數的構造方法中。
- byName:被注入bean的id名必須與set方法后半截匹配,并且id名稱的第一個單詞首字母必須小寫,這一點與手動set注入有點不同。
- byType:查找所有的set方法,將符合符合參數類型的bean注入。
下面進入正題:
注解方式注冊bean:
在以前的開發中,我們主要使用四種注解注冊bean,每種注解可以任意使用,只是語義上有所差異:
隨著springboot的流行,@Bean注解也逐漸的被我們使用起來。Spring的@Bean注解用于告訴方法,產生一個Bean對象,然后這個Bean對象交給Spring管理。產生這個Bean對象的方法Spring只會調用一次,隨后這個Spring將會將這個Bean對象放在自己的IOC容器中。
注解方式注入依賴(主要有兩種):
- @Resource :java的注解,默認以byName的方式去匹配與屬性名相同的bean的id,如果沒有找到就會以byType的方式查找,如果byType查找到多個的話,使用@Qualifier注解(spring注解)指定某個具體名稱的bean。
- @Autowired :spring注解,默認是以byType的方式去匹配類型相同的bean,可以結合@Qualifier 注解根據byName方式匹配。
關于他們的具體用法與區別,因為內容比較多,所以寫在另一篇博客中,請見:@Autowired 和 @Resource 詳解
2.2 構造器注入
老規矩,先上代碼示例:
@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);} }這里有一個問題,如果只有一個有參數的構造方法并且參數類型與注入的bean的類型匹配,那就會注入到該構造方法中。如果有多個有參數的構造方法并且每個構造方法的參數列表里面都有要注入的屬性,那userDaoJdbc會注入到哪里呢?
?
在Spring4.x版本中推薦的注入方式就是這種,相較于上面的field注入方式而言,就顯得有點難看,特別是當注入的依賴很多(5個以上)的時候,就會明顯的發現代碼顯得很臃腫。對于從field注入轉過來+有強迫癥的同學來說,簡直可以說是石樂志 ,但是為啥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團隊通常提倡構造函數注入,因為它允許將應用程序組件實現為不可變的對象,并確保所需的依賴不為空。此外,注入構造函數的組件總是以完全初始化的狀態返回給客戶機(調用)代碼。
簡單解釋一下:
- 不可變的對象:其實說的就是final關鍵字,這里不再多解釋了。
- 依賴不為空:省去了我們對其檢查。當要實例化ConstructorController的時候,由于自己實現了有參數的構造函數,所以不會調用默認構造函數,那么就需要Spring容器傳入所需要的參數,所以就兩種情況:1、有該類型的參數->傳入,OK 。2:無該類型的參數->報錯。這樣就可以保證不會為空。
- 完全初始化的狀態:這個可以跟上面的依賴不為空結合起來,向構造器傳參之前,要確保注入的內容不為空,那么肯定要調用依賴組件的構造方法完成實例化。而在Java類加載實例化的過程中,構造方法是最后一步(之前如果有父類先初始化父類,然后自己的成員變量,最后才是構造方法,這里不詳細展開。)。所以返回來的都是初始化之后的狀態。
與注解方式注入相比,構造器注入可復用性高,如果使用field注入,缺點顯而易見,對于IOC容器以外的環境,除了使用反射來提供它需要的依賴之外,無法復用該實現類。而且將一直是個潛在的隱患,因為你不調用將一直無法發現NPE的存在。
相對于注解注入,構造器注入可以防止循環依賴的問題,若如下代碼:
public class A {@Autowiredprivate B b; }public class B {@Autowiredprivate A a; }如果使用構造器注入,在spring項目啟動的時候,就會拋出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?從而提醒你避免循環依賴,如果是注解注入的話,啟動的時候不會報錯,在使用那個bean的時候才會報錯。??
2.3 setter注入
這是在spring3.x出來的時候,官方推薦的注入方式,但是在spring4.x以后就沒有見它推薦了,而且在實際開發中已經很少能見到這種注入方式了。下面來看一下它的實現:
@RestController @RequestMapping("/setter") public class SetterController {private DiService diService;@Autowiredpublic void setDiService(DiService diService) {this.diService = diService;}@GetMapping("/test001")public String test001() {return diService.test001("setter");} }試想一下,一旦需要注入的組件很多,那我們會累死的,所以大家都不喜歡用它也是情理之中的事情。
這里有一點需要注意:如果通過set方法注入屬性,那么spring會通過默認的空參構造方法來實例化對象,所以如果在類中寫了一個帶有參數的構造方法,一定要把空參數的構造方法寫上,否則spring沒有辦法實例化對象,導致報錯。
總結
這么多的依賴注入方式,我們應該怎么選擇呢?那種方式最好呢?
其實,有句古話說的很對,合適自己的才是最好的,我們需要看情況來選擇使用哪種注入方式。
使用構造器注入的好處:
另外,當有一個依賴有多個實現的使用,推薦使用注解方式注入的方式來指定注入的類型或name,使用setter注入指定類型。這是spring官方博客對setter注入方式和構造器注入的比較。謝謝大家看完了,如果有描述不對的地方歡迎指正,與大家共同進步!
總結
以上是生活随笔為你收集整理的spring DI的三种注入方式分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【DB笔试面试1-100】
- 下一篇: Spring之DI依赖注入