目錄
- 前言
- 常見的三種注入方式
- 構造器注入的好處
一. 前言
Spring框架對Java開發的重要性不言而喻,其核心特性就IOC(Inversion of Control)和AOP,平時使用最多的就是其中的IOC,我們通過將組件交由Spring的IOC容器管理,將對象的依賴關系由Spring控制,避免硬編碼所造成的過度程序耦合。剛到實習的公司被安排測試和修改一些BUG,在查看源代碼的時候看到公司的代碼Controller中使用的注入service的方式和以前自己常用的注入方式不同,所以本着探究到底的心情,上網搜索了一些相關博客,下面是筆者通過總結相關博客來比較一下常見的三種注入方式。
二. 常見的三種注入方式
這里我們只用注解的方式進行注入(嘿嘿,現在估計大家很少用xml了,所以這里就用注解的方式了)
2.1 field注入方式
@Controller
public class FooController {
@Autowired
private UserService userService;
public List<user> listuser() {
return userService.list();
}
}
這種注入方式是筆者之前做常用的注入方式,原因很簡單因為筆者很懶,這種注入方式:
- 注入方式簡單:只需要加入要注入的字段,附上
@Autowired
,即可完成。 - 整體代碼簡潔明了,美觀大方。
2.2 構造器注入方式
@Controller
public class FooController {
private final UserService userService;
@Autowired
public FooController(UserService userService) {
this.userService = userService;
}
}
在Spring4.x版本中推薦的注入方式就是這種,相較於上面的field注入方式而言,就顯得有點難看,特別是當注入的依賴很多(4個以上)的時候,就會明顯的發現代碼顯得很臃腫。對於從field注入轉過來+有強迫症的碼友來說,簡直可以說是難受的一批。
2.3 setter注入方式
@Controller
public class FooController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
在Spring3.x剛推出的時候,推薦使用注入的就是這種,寫起來麻煩,當初推薦Spring自然也有他的道理,這里我們引用一下Spring當時的原話:
The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.
Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.
本人英文實在不好,六級至今未過,直接借用一下原博主的原話,構造器注入參數太多,顯得太笨重,另外setter的方式能夠讓類在之后重新配置或者重新注入。
然而::真香定律::誰也逃不過后面Spring為什么又換成構造器注入了呢?
三. 構造器注入的好處
首先我們來看一下Spring官方文檔里是怎么說的:
The Spring team generally advocates constructor injection as it enables one to implement application components asimmutable objectsand to ensure that required dependencies are
not null
. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
簡單的翻譯一下:這個構造器注入的方式啊,能夠保證注入的組件不可變,並且確保需要的依賴不為空。此外,構造器注入的依賴總是能夠在返回客戶端(組件)代碼的時候保證完全初始化的狀態。
下面來簡單的解釋一下:
- 依賴不可變:其實說的就是final關鍵字,這里不再多解釋了。不明白的園友可以回去看看Java語法。
- 依賴不為空(省去了我們對其檢查):當要實例化FooController的時候,由於自己實現了有參數的構造函數,所以不會調用默認構造函數,那么就需要Spring容器傳入所需要的參數,所以就兩種情況:1、有該類型的參數->傳入,OK 。2:無該類型的參數->報錯。所以保證不會為空,Spring總不至於傳一個null進去吧 😦
- 完全初始化的狀態:這個可以跟上面的依賴不為空結合起來,向構造器傳參之前,要確保注入的內容不為空,那么肯定要調用依賴組件的構造方法完成實例化。而在Java類加載實例化的過程中,構造方法是最后一步(之前如果有父類先初始化父類,然后自己的成員變量,最后才是構造方法,這里不詳細展開。)。所以返回來的都是初始化之后的狀態。
比較完了setter注入與構造器注入的優缺點,你還沒用說使用field注入與構造器的比較呢!那么我們再回頭看一看使用最多的field注入方式:
//承接上面field注入的代碼,假如客戶端代碼使用下面的調用(或者再Junit測試中使用)
//這里只是模擬一下,正常來說我們只會暴露接口給客戶端,不會暴露實現。
FooController fooController = new FooController();
fooController.listFoo(); // -> NullPointerException
如果使用field注入,缺點顯而易見,**對於IOC容器以外的環境,除了使用反射來提供它需要的依賴之外,無法復用該實現類**。而且將一直是個潛在的隱患,因為你不調用將一直無法發現NPE的存在。
還值得一提另外一點是:使用field注入可能會導致循環依賴,即A里面注入B,B里面又注入A:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
**如果使用構造器注入,在spring項目啟動的時候,就會拋出BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?從而提醒你避免循環依賴,如果是field注入的話,啟動的時候不會報錯,在使用那個bean的時候才會報錯**。