最全的 Spring 依賴(lài)注入方式,你都會(huì)了嗎?
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)??

前言
Spring 正如其名字,給開(kāi)發(fā)者帶來(lái)了春天,Spring 是為解決企業(yè)級(jí)應(yīng)用開(kāi)發(fā)的復(fù)雜性而設(shè)計(jì)的一款框架,其設(shè)計(jì)理念就是:簡(jiǎn)化開(kāi)發(fā)。
Spring 框架中最核心思想就是:
- IOC(控制反轉(zhuǎn)): 即轉(zhuǎn)移創(chuàng)建對(duì)象的控制權(quán),將創(chuàng)建對(duì)象的控制權(quán)從開(kāi)發(fā)者轉(zhuǎn)移到了 Spring 框架。
- AOP(切面編程): 將公共行為(如記錄日志,權(quán)限校驗(yàn)等)封裝到可重用的模塊中,而使原本的模塊內(nèi)只需關(guān)注自身的個(gè)性化行為。
本文,將主要介紹 Spring 中 IOC 的依賴(lài)注入,
控制反轉(zhuǎn) IOC
就 IOC 本身而言,其并不是什么新技術(shù),只是一種思想理念。IOC 的核心就是原先創(chuàng)建一個(gè)對(duì)象,我們需要自己直接通過(guò) new 來(lái)創(chuàng)建,而 IOC 就相當(dāng)于有人幫們創(chuàng)建好了對(duì)象,需要使用的時(shí)候直接去拿就行,IOC 主要有兩種實(shí)現(xiàn)方式:
- DL(Dependency Lookup):依賴(lài)查找。
這種就是說(shuō)容器幫我們創(chuàng)建好了對(duì)象,我們需要使用的時(shí)候自己再主動(dòng)去容器中查找,如:
ApplicationContext?applicationContext?=?new?ClassPathXmlApplicationContext("/application-context.xml");
Object?bean?=?applicationContext.getBean("object");
- DI(Dependency Inject):依賴(lài)注入。
依賴(lài)注入相比較依賴(lài)查找又是一種優(yōu)化,也就是我們不需要自己去查找,只需要告訴容器當(dāng)前需要注入的對(duì)象,容器就會(huì)自動(dòng)將創(chuàng)建好的對(duì)象進(jìn)行注入(賦值)。
依賴(lài)注入 DI
通過(guò) xml 的注入方式我們不做討論,在這里主要討論基于注解的注入方式,基于注解的常規(guī)注入方式通常有三種:
- 基于屬性注入
- 基于 setter 方法注入
- 基于構(gòu)造器注入
三種常規(guī)注入方式
接下來(lái)就讓我們分別介紹一下三種常規(guī)的注入方式。
屬性注入
通過(guò)屬性注入的方式非常常用,這個(gè)應(yīng)該是大家比較熟悉的一種方式:
@Service
public?class?UserService?{
????@Autowired
????private?Wolf1Bean?wolf1Bean;//通過(guò)屬性注入
}
setter 方法注入
除了通過(guò)屬性注入,通過(guò) setter 方法也可以實(shí)現(xiàn)注入:
@Service
public?class?UserService?{
????private?Wolf3Bean?wolf3Bean;
????
????@Autowired??//通過(guò)setter方法實(shí)現(xiàn)注入
????public?void?setWolf3Bean(Wolf3Bean?wolf3Bean)?{
????????this.wolf3Bean?=?wolf3Bean;
????}
}
構(gòu)造器注入
當(dāng)兩個(gè)類(lèi)屬于強(qiáng)關(guān)聯(lián)時(shí),我們也可以通過(guò)構(gòu)造器的方式來(lái)實(shí)現(xiàn)注入:
@Service
public?class?UserService?{
??private?Wolf2Bean?wolf2Bean;
????
?????@Autowired?//通過(guò)構(gòu)造器注入
????public?UserService(Wolf2Bean?wolf2Bean)?{
????????this.wolf2Bean?=?wolf2Bean;
????}
}
接口注入
在上面的三種常規(guī)注入方式中,假如我們想要注入一個(gè)接口,而當(dāng)前接口又有多個(gè)實(shí)現(xiàn)類(lèi),那么這時(shí)候就會(huì)報(bào)錯(cuò),因?yàn)?Spring 無(wú)法知道到底應(yīng)該注入哪一個(gè)實(shí)現(xiàn)類(lèi)。
比如我們上面的三個(gè)類(lèi)全部實(shí)現(xiàn)同一個(gè)接口 IWolf,那么這時(shí)候直接使用常規(guī)的,不帶任何注解元數(shù)據(jù)的注入方式來(lái)注入接口 IWolf。
@Autowired
private?IWolf?iWolf;
此時(shí)啟動(dòng)服務(wù)就會(huì)報(bào)錯(cuò):
圖片這個(gè)就是說(shuō)本來(lái)應(yīng)該注入一個(gè)類(lèi),但是 Spring 找到了三個(gè),所以沒(méi)法確認(rèn)到底應(yīng)該用哪一個(gè)。這個(gè)問(wèn)題如何解決呢?
解決思路主要有以下 5 種:
通過(guò)配置文件和 @ConditionalOnProperty 注解實(shí)現(xiàn)
通過(guò) @ConditionalOnProperty 注解可以結(jié)合配置文件來(lái)實(shí)現(xiàn)唯一注入。下面示例就是說(shuō)如果配置文件中配置了 lonely.wolf=test1,那么就會(huì)將 Wolf1Bean 初始化到容器,此時(shí)因?yàn)槠渌麑?shí)現(xiàn)類(lèi)不滿(mǎn)足條件,所以不會(huì)被初始化到 IOC 容器,所以就可以正常注入接口:
@Component
@ConditionalOnProperty(name?=?"lonely.wolf",havingValue?=?"test1")
public?class?Wolf1Bean?implements?IWolf{
}
當(dāng)然,這種配置方式,編譯器可能還是會(huì)提示有多個(gè) Bean,但是只要我們確保每個(gè)實(shí)現(xiàn)類(lèi)的條件不一致,就可以正常使用。
通過(guò)其他 @Condition 條件注解
除了上面的配置文件條件,還可以通過(guò)其他類(lèi)似的條件注解,如:
- @ConditionalOnBean:當(dāng)存在某一個(gè) Bean 時(shí),初始化此類(lèi)到容器。
- @ConditionalOnClass:當(dāng)存在某一個(gè)類(lèi)時(shí),初始化此類(lèi)的容器。
- @ConditionalOnMissingBean:當(dāng)不存在某一個(gè) Bean 時(shí),初始化此類(lèi)到容器。
- @ConditionalOnMissingClass:當(dāng)不存在某一個(gè)類(lèi)時(shí),初始化此類(lèi)到容器。
- …
類(lèi)似這種實(shí)現(xiàn)方式也可以非常靈活的實(shí)現(xiàn)動(dòng)態(tài)化配置。
不過(guò)上面介紹的這些方法似乎每次都只能固定注入一個(gè)實(shí)現(xiàn)類(lèi),那么如果我們就是想多個(gè)類(lèi)同時(shí)注入,不同的場(chǎng)景可以動(dòng)態(tài)切換而又不需要重啟或者修改配置文件,又該如何實(shí)現(xiàn)呢?
通過(guò) @Resource 注解動(dòng)態(tài)獲取
如果不想手動(dòng)獲取,我們也可以通過(guò) @Resource 注解的形式動(dòng)態(tài)指定 BeanName 來(lái)獲?。?/p>
@Component
public?class?InterfaceInject?{
????@Resource(name?=?"wolf1Bean")
????private?IWolf?iWolf;
}
如上所示則只會(huì)注入 BeanName 為 wolf1Bean 的實(shí)現(xiàn)類(lèi)。
通過(guò)集合注入
除了指定 Bean 的方式注入,我們也可以通過(guò)集合的方式一次性注入接口的所有實(shí)現(xiàn)類(lèi):
@Component
public?class?InterfaceInject?{
????@Autowired
????List?list;
????@Autowired
????private?Map?map;
}
上面的兩種形式都會(huì)將 IWolf 中所有的實(shí)現(xiàn)類(lèi)注入集合中。如果使用的是 List 集合,那么我們可以取出來(lái)再通過(guò) instanceof 關(guān)鍵字來(lái)判定類(lèi)型;而通過(guò) Map 集合注入的話(huà),Spring 會(huì)將 Bean 的名稱(chēng)(默認(rèn)類(lèi)名首字母小寫(xiě))作為 key 來(lái)存儲(chǔ),這樣我們就可以在需要的時(shí)候動(dòng)態(tài)獲取自己想要的實(shí)現(xiàn)類(lèi)。
@Primary 注解實(shí)現(xiàn)默認(rèn)注入
除了上面的幾種方式,我們還可以在其中某一個(gè)實(shí)現(xiàn)類(lèi)上加上 @Primary 注解來(lái)表示當(dāng)有多個(gè) Bean 滿(mǎn)足條件時(shí),優(yōu)先注入當(dāng)前帶有 @Primary 注解的 Bean:
@Component
@Primary
public?class?Wolf1Bean?implements?IWolf{
}
通過(guò)這種方式,Spring 就會(huì)默認(rèn)注入 wolf1Bean,而同時(shí)我們?nèi)匀豢梢酝ㄟ^(guò)上下文手動(dòng)獲取其他實(shí)現(xiàn)類(lèi),因?yàn)槠渌麑?shí)現(xiàn)類(lèi)也存在容器中。
手動(dòng)獲取 Bean 的幾種方式
在 Spring 項(xiàng)目中,手動(dòng)獲取 Bean 需要通過(guò) ApplicationContext 對(duì)象,這時(shí)候可以通過(guò)以下 5 種方式進(jìn)行獲?。?/p>
直接注入
最簡(jiǎn)單的一種方法就是通過(guò)直接注入的方式獲取 ApplicationContext 對(duì)象,然后就可以通過(guò) ApplicationContext 對(duì)象獲取 Bean :
@Component
public?class?InterfaceInject?{
????@Autowired
????private?ApplicationContext?applicationContext;//注入
????public?Object?getBean(){
????????return?applicationContext.getBean("wolf1Bean");//獲取bean
????}
}
通過(guò) ApplicationContextAware 接口獲取
通過(guò)實(shí)現(xiàn) ApplicationContextAware 接口來(lái)獲取 ApplicationContext 對(duì)象,從而獲取 Bean。需要注意的是,實(shí)現(xiàn) ApplicationContextAware 接口的類(lèi)也需要加上注解,以便交給 Spring 統(tǒng)一管理(這種方式也是項(xiàng)目中使用比較多的一種方式):
@Component
public?class?SpringContextUtil?implements?ApplicationContextAware?{
????private?static?ApplicationContext?applicationContext?=?null;
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????this.applicationContext?=?applicationContext;
????}
????/**
?????*?通過(guò)名稱(chēng)獲取bean
?????*/
????public?static?T?getBeanByName(String?beanName){
????????return?(T)?applicationContext.getBean(beanName);
????}
????/**
?????*?通過(guò)類(lèi)型獲取bean
?????*/
????public?static?T?getBeanByType(Class?clazz){
????????return?(T)?applicationContext.getBean(clazz);
????}
}
封裝之后,我們就可以直接調(diào)用對(duì)應(yīng)的方法獲取 Bean 了:
Wolf2Bean?wolf2Bean?=?SpringContextUtil.getBeanByName("wolf2Bean");
Wolf3Bean?wolf3Bean?=?SpringContextUtil.getBeanByType(Wolf3Bean.class);
通過(guò) ApplicationObjectSupport 和 WebApplicationObjectSupport 獲取
這兩個(gè)對(duì)象中,WebApplicationObjectSupport 繼承了 ApplicationObjectSupport,所以并無(wú)實(shí)質(zhì)的區(qū)別。
同樣的,下面這個(gè)工具類(lèi)也需要增加注解,以便交由 Spring 進(jìn)行統(tǒng)一管理:
@Component
public?class?SpringUtil?extends?/*WebApplicationObjectSupport*/?ApplicationObjectSupport?{
????private?static?ApplicationContext?applicationContext?=?null;
????public?static?T?getBean(String?beanName){
????????return?(T)?applicationContext.getBean(beanName);
????}
????@PostConstruct
????public?void?init(){
????????applicationContext?=?super.getApplicationContext();
????}
}
有了工具類(lèi),在方法中就可以直接調(diào)用了:
@RestController
@RequestMapping("/hello")
@Qualifier
public?class?HelloController?{
????@GetMapping("/bean3")
????public?Object?getBean3(){
????????Wolf1Bean?wolf1Bean?=?SpringUtil.getBean("wolf1Bean");
????????return?wolf1Bean.toString();
????}
}
通過(guò) HttpServletRequest 獲取
通過(guò) HttpServletRequest 對(duì)象,再結(jié)合 Spring 自身提供的工具類(lèi) WebApplicationContextUtils 也可以獲取到 ApplicationContext 對(duì)象,而 HttpServletRequest 對(duì)象可以主動(dòng)獲?。ㄈ缦?getBean2 方法),也可以被動(dòng)獲?。ㄈ缦?getBean1 方法):
@RestController
@RequestMapping("/hello")
@Qualifier
public?class?HelloController?{
????@GetMapping("/bean1")
????public?Object?getBean1(HttpServletRequest?request){
????????//直接通過(guò)方法中的HttpServletRequest對(duì)象
????????ApplicationContext?applicationContext?=?WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
????????Wolf1Bean?wolf1Bean?=?(Wolf1Bean)applicationContext.getBean("wolf1Bean");
????????return?wolf1Bean.toString();
????}
????@GetMapping("/bean2")
????public?Object?getBean2(){
????????HttpServletRequest?request?=?((ServletRequestAttributes)?RequestContextHolder.getRequestAttributes()).getRequest();//手動(dòng)獲取request對(duì)象
????????ApplicationContext?applicationContext?=?WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
????????Wolf2Bean?wolf2Bean?=?(Wolf2Bean)applicationContext.getBean("wolf2Bean");
????????return?wolf2Bean.toString();
????}
}
其他方式獲取
當(dāng)然,除了上面提到的方法,我們也可以使用最開(kāi)始提到的 DL 中代碼示例去手動(dòng) new 一個(gè) ApplicationContext 對(duì)象,但是這樣就意味著重新初始化了一次,所以是不建議這么去做,但是在寫(xiě)單元測(cè)試的時(shí)候這種方式是比較適合的。
談?wù)?@Autowrite 和 @Resource 以及 @Qualifier 注解的區(qū)別
上面我們看到了,注入一個(gè) Bean 可以通過(guò) @Autowrite,也可以通過(guò) @Resource 注解來(lái)注入,這兩個(gè)注解有什么區(qū)別呢?
@Autowrite:通過(guò)類(lèi)型去注入,可以用于構(gòu)造器和參數(shù)注入。當(dāng)我們注入接口時(shí),其所有的實(shí)現(xiàn)類(lèi)都屬于同一個(gè)類(lèi)型,所以就沒(méi)辦法知道選擇哪一個(gè)實(shí)現(xiàn)類(lèi)來(lái)注入。@Resource:默認(rèn)通過(guò)名字注入,不能用于構(gòu)造器和參數(shù)注入。如果通過(guò)名字找不到唯一的 Bean,則會(huì)通過(guò)類(lèi)型去查找。如下可以通過(guò)指定 name 或者 type 來(lái)確定唯一的實(shí)現(xiàn):
@Resource(name?=?"wolf2Bean",type?=?Wolf2Bean.class)
?private?IWolf?iWolf;
而 @Qualifier 注解是用來(lái)標(biāo)識(shí)合格者,當(dāng) @Autowrite 和 @Qualifier 一起使用時(shí),就相當(dāng)于是通過(guò)名字來(lái)確定唯一:
@Qualifier("wolf1Bean")
@Autowired
private?IWolf?iWolf;
那可能有人就會(huì)說(shuō),我直接用 @Resource 就好了,何必用兩個(gè)注解結(jié)合那么麻煩,這么一說(shuō)似乎顯得 @Qualifier 注解有點(diǎn)多余?
@Qualifier 注解是多余的嗎
我們先看下面聲明 Bean 的場(chǎng)景,這里通過(guò)一個(gè)方法來(lái)聲明一個(gè) Bean (MyElement),而且方法中的參數(shù)又有 Wolf1Bean 對(duì)象,那么這時(shí)候 Spring 會(huì)幫我們自動(dòng)注入 Wolf1Bean:
@Component
public?class?InterfaceInject2?{
????@Bean
????public?MyElement?test(Wolf1Bean?wolf1Bean){
????????return?new?MyElement();
????}
}
然而如果說(shuō)我們把上面的代碼稍微改一下,把參數(shù)改成一個(gè)接口,而接口又有多個(gè)實(shí)現(xiàn)類(lèi),這時(shí)候就會(huì)報(bào)錯(cuò)了:
@Component
public?class?InterfaceInject2?{
????@Bean
????public?MyElement?test(IWolf?iWolf){//此時(shí)因?yàn)镮Wolf接口有多個(gè)實(shí)現(xiàn)類(lèi),會(huì)報(bào)錯(cuò)
????????return?new?MyElement();
????}
}
而 @Resource 注解又是不能用在參數(shù)中,所以這時(shí)候就需要使用 @Qualifier 注解來(lái)確認(rèn)唯一實(shí)現(xiàn)了(比如在配置多數(shù)據(jù)源的時(shí)候就經(jīng)常使用 @Qualifier 注解來(lái)實(shí)現(xiàn)):
@Component
public?class?InterfaceInject2?{
????@Bean
????public?MyElement?test(@Qualifier("wolf1Bean")?IWolf?iWolf){
????????return?new?MyElement();
????}
}
總結(jié)
本文主要講述了如何在 Spring 中使用靈活的方式來(lái)實(shí)現(xiàn)各種場(chǎng)景的注入方式,并且著重介紹了當(dāng)一個(gè)接口有多個(gè)實(shí)現(xiàn)類(lèi)時(shí)應(yīng)該如何注入的問(wèn)題,最后也介紹了常用幾個(gè)注入注解的區(qū)別,通過(guò)本文,相信大家對(duì)如何使用 Spring 中的依賴(lài)注入會(huì)更加的熟悉。
來(lái)源:blog.csdn.net/zwx900102/article/details/115023405
1.?烏迪爾 ! 小哈花了一分鐘就給博客添加了頁(yè)面加載進(jìn)度條 !
2.?SpringBoot服務(wù)監(jiān)控機(jī)制,總算整明白了!
4.?為什么 Redis 集群要使用反向代理? 看這篇就明白了!
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
PS:因公眾號(hào)平臺(tái)更改了推送規(guī)則,如果不想錯(cuò)過(guò)內(nèi)容,記得讀完點(diǎn)一下“在看”,加個(gè)“星標(biāo)”,這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。
點(diǎn)“在看”支持小哈呀,謝謝啦??
