Spring注解使用以及相關(guān)實(shí)現(xiàn)原理

使用 @Repository子寓、@Service收叶、@Controller 和 @Component 將類標(biāo)識(shí)為 Bean

Spring 自 2.0 版本開始,陸續(xù)引入了一些注解用于簡化 Spring 的開發(fā)惜傲。

  • @Repository 注解便屬于最先引入的一批屡贺,它用于將數(shù)據(jù)訪問層 (DAO 層 ) 的類標(biāo)識(shí)為 Spring Bean妥凳。

具體只需將該注解標(biāo)注在 DAO 類上即可竟贯。同時(shí),為了讓 Spring 能夠掃描類路徑中的類并識(shí)別出 @Repository 注解逝钥,需要在 XML 配置文件中啟用 Bean 的自動(dòng)掃描功能屑那,這可以通過 <context:component-scan/> 實(shí)現(xiàn)。如下所示:

// 首先使用 @Repository 將 DAO 類聲明為 Bean 
 package bookstore.dao; 
 @Repository 
 public class UserDaoImpl implements UserDao{ …… } 
 // 其次晌缘,在 XML 配置文件中啟動(dòng) Spring 的自動(dòng)掃描功能
 <beans … > 
    ……
 <context:component-scan base-package=”bookstore.dao” /> 
……
 </beans>

如此齐莲,我們就不再需要在 XML 中顯式使用 <bean/> 進(jìn)行 Bean 的配置。Spring 在容器初始化時(shí)將自動(dòng)掃描 base-package 指定的包及其子包下的所有 class 文件磷箕,所有標(biāo)注了 @Repository 的類都將被注冊(cè)為 Spring Bean。

為什么 @Repository 只能標(biāo)注在 DAO 類上呢阵难?

這是因?yàn)樵撟⒔獾淖饔貌恢皇菍㈩愖R(shí)別為 Bean岳枷,同時(shí)它還能將所標(biāo)注的類中拋出的數(shù)據(jù)訪問異常封裝為 Spring 的數(shù)據(jù)訪問異常類型。 Spring 本身提供了一個(gè)豐富的并且是與具體的數(shù)據(jù)訪問技術(shù)無關(guān)的數(shù)據(jù)訪問異常結(jié)構(gòu)呜叫,用于封裝不同的持久層框架拋出的異常空繁,使得異常獨(dú)立于底層的框架。

Spring 2.5 在 @Repository 的基礎(chǔ)上增加了功能類似的額外三個(gè)注解:@Component朱庆、@Service盛泡、@Constroller,它們分別用于軟件系統(tǒng)的不同層次:

  • @Component 是一個(gè)泛化的概念娱颊,僅僅表示一個(gè)組件 (Bean) 傲诵,可以作用在任何層次凯砍。

  • @Service 通常作用在業(yè)務(wù)層,但是目前該功能與 @Component相同拴竹。

  • @Constroller 通常作用在控制層悟衩,但是目前該功能與 @Component 相同。

通過在類上使用 @Repository栓拜、@Component座泳、@Service@Constroller 注解,Spring 會(huì)自動(dòng)創(chuàng)建相應(yīng)的 BeanDefinition 對(duì)象幕与,并注冊(cè)到 ApplicationContext 中挑势。這些類就成了 Spring 受管組件。這三個(gè)注解除了作用于不同軟件層次的類啦鸣,其使用方式與 @Repository是完全相同的潮饱。

  • 自定義注解。

另外赏陵,除了上面的四個(gè)注解外饼齿,用戶可以創(chuàng)建自定義的注解,然后在注解上標(biāo)注 @Component蝙搔,那么缕溉,該自定義注解便具有了與所 @Component 相同的功能。不過這個(gè)功能并不常用吃型。

命名策略

當(dāng)一個(gè) Bean 被自動(dòng)檢測到時(shí)证鸥,會(huì)根據(jù)那個(gè)掃描器的 BeanNameGenerator 策略生成它的 bean 名稱。默認(rèn)情況下勤晚,對(duì)于包含 name 屬性的 @Component枉层、@Repository@Service@Controller赐写,會(huì)把 name 取值作為 Bean 的名字鸟蜡。如果這個(gè)注解不包含 name 值或是其他被自定義過濾器發(fā)現(xiàn)的組件,默認(rèn) Bean 名稱會(huì)是小寫開頭的非限定類名挺邀。如果你不想使用默認(rèn) bean 命名策略揉忘,可以提供一個(gè)自定義的命名策略。首先實(shí)現(xiàn) BeanNameGenerator 接口端铛,確認(rèn)包含了一個(gè)默認(rèn)的無參數(shù)構(gòu)造方法泣矛。然后在配置掃描器時(shí)提供一個(gè)全限定類名,如下所示:

<beans ...> 
<context:component-scan
   base-package="a.b" name-generator="a.SimpleNameGenerator"/> 
</beans>

作用域控制

與通過 XML 配置的 Spring Bean 一樣禾蚕,通過上述注解標(biāo)識(shí)的 Bean您朽,其默認(rèn)作用域是"singleton",為了配合這四個(gè)注解换淆,在標(biāo)注 Bean 的同時(shí)能夠指定 Bean 的作用域哗总,Spring 2.5 引入了 @Scope 注解几颜。使用該注解時(shí)只需提供作用域的名稱就行了,如下所示:

@Scope("prototype") 
@Repository 
public class Demo { … }

如果你想提供一個(gè)自定義的作用域解析策略而不使用基于注解的方法魂奥,只需實(shí)現(xiàn) ScopeMetadataResolver 接口菠剩,確認(rèn)包含一個(gè)默認(rèn)的沒有參數(shù)的構(gòu)造方法。然后在配置掃描器時(shí)提供全限定類名:

<context:component-scan base-package="a.b"
scope-resolver="footmark.SimpleScopeResolver" />

生命周期回調(diào)方法

使用 @PostConstruct@PreDestroy 指定生命周期回調(diào)方法
Spring Bean 是受 Spring IoC 容器管理耻煤,由容器進(jìn)行初始化和銷毀的(prototype 類型由容器初始化之后便不受容器管理)具壮,通常我們不需要關(guān)注容器對(duì) Bean 的初始化和銷毀操作,由 Spring 經(jīng)過構(gòu)造函數(shù)或者工廠方法創(chuàng)建的 Bean 就是已經(jīng)初始化完成并立即可用的哈蝇。然而在某些情況下棺妓,可能需要我們手工做一些額外的初始化或者銷毀操作,這通常是針對(duì)一些資源的獲取和釋放操作炮赦。Spring 1.x 為此提供了兩種方式供用戶指定執(zhí)行生命周期回調(diào)的方法怜跑。

第一種方式是實(shí)現(xiàn) Spring 提供的兩個(gè)接口:InitializingBean 和 DisposableBean。如果希望在 Bean 初始化完成之后執(zhí)行一些自定義操作吠勘,則可以讓 Bean 實(shí)現(xiàn) InitializingBean 接口性芬,該接口包含一個(gè) afterPropertiesSet() 方法,容器在為該 Bean 設(shè)置了屬性之后剧防,將自動(dòng)調(diào)用該方法植锉;如果 Bean 實(shí)現(xiàn)了 DisposableBean 接口,則容器在銷毀該 Bean 之前峭拘,將調(diào)用該接口的 destroy() 方法俊庇。這種方式的缺點(diǎn)是,讓 Bean 類實(shí)現(xiàn) Spring 提供的接口鸡挠,增加了代碼與 Spring 框架的耦合度辉饱,因此不推薦使用。

第二種方式是在 XML 文件中使用 <bean> 的 init-method 和 destroy-method 屬性指定初始化之后和銷毀之前的回調(diào)方法拣展,代碼無需實(shí)現(xiàn)任何接口彭沼。這兩個(gè)屬性的取值是相應(yīng) Bean 類中的初始化和銷毀方法,方法名任意备埃,但是方法不能有參數(shù)溜腐。示例如下:

<bean id=”userService” 
class=”bookstore.service.UserService” 
init-method=”init” destroy-method=”destroy”> 
   …
</bean>

Spring 2.5 在保留以上兩種方式的基礎(chǔ)上,提供了對(duì) JSR-250 的支持瓜喇。JSR-250 規(guī)范定義了兩個(gè)用于指定聲明周期方法的注解:@PostConstruct@PreDestroy。這兩個(gè)注解使用非常簡單歉糜,只需分別將他們標(biāo)注于初始化之后執(zhí)行的回調(diào)方法或者銷毀之前執(zhí)行的回調(diào)方法上乘寒。由于使用了注解,因此需要配置相應(yīng)的 Bean 后處理器匪补,亦即在 XML 中增加如下一行:

<context:annotation-config /

比較上述三種指定生命周期回調(diào)方法的方式伞辛,第一種是不建議使用的烂翰,不但其用法不如后兩種方式靈活,而且無形中增加了代碼與框架的耦合度蚤氏。后面兩種方式開發(fā)者可以根據(jù)使用習(xí)慣選擇其中一種甘耿,但是最好不要混合使用,以免增加維護(hù)的難度竿滨。

依賴檢查

使用 @Required 進(jìn)行 Bean 的依賴檢查
依賴檢查的作用是佳恬,判斷給定 Bean 的相應(yīng) Setter 方法是否都在實(shí)例化的時(shí)候被調(diào)用了。而不是判斷字段是否已經(jīng)存在值了于游。Spring 進(jìn)行依賴檢查時(shí)毁葱,只會(huì)判斷屬性是否使用了 Setter 注入。如果某個(gè)屬性沒有使用 Setter 注入贰剥,即使是通過構(gòu)造函數(shù)已經(jīng)為該屬性注入了值倾剿,Spring 仍然認(rèn)為它沒有執(zhí)行注入,從而拋出異常蚌成。另外前痘,Spring 只管是否通過 Setter 執(zhí)行了注入,而對(duì)注入的值卻沒有任何要求担忧,即使注入的 <null/>芹缔,Spring 也認(rèn)為是執(zhí)行了依賴注入。

<bean> 標(biāo)簽提供了 dependency-check 屬性用于進(jìn)行依賴檢查涵妥。該屬性的取值包括以下幾種:

none -- 默認(rèn)不執(zhí)行依賴檢查乖菱。可以在 <beans> 標(biāo)簽上使用 default-dependency-check 屬性改變默認(rèn)值蓬网。
simple -- 對(duì)原始基本類型和集合類型進(jìn)行檢查窒所。
objects -- 對(duì)復(fù)雜類型進(jìn)行檢查(除了 simple 所檢查類型之外的其他類型)。
all -- 對(duì)所有類型進(jìn)行檢查帆锋。
舊版本使用 dependency-check 在配置文件中設(shè)置吵取,缺點(diǎn)是粒度較粗。使用 Spring2.0 提供的 @Required 注解锯厢,提供了更細(xì)粒度的控制皮官。@Required 注解只能標(biāo)注在 Setter 方法之上。因?yàn)橐蕾囎⑷氲谋举|(zhì)是檢查 Setter 方法是否被調(diào)用了实辑,而不是真的去檢查屬性是否賦值了以及賦了什么樣的值捺氢。如果將該注解標(biāo)注在非 setXxxx() 類型的方法則被忽略。

為了讓 Spring 能夠處理該注解剪撬,需要激活相應(yīng)的 Bean 后處理器摄乒。要激活該后處理器,只需在 XML 中增加如下一行即可。

<context:annotation-config/>

當(dāng)某個(gè)被標(biāo)注了 @Required 的 Setter 方法沒有被調(diào)用馍佑,則 Spring 在解析的時(shí)候會(huì)拋出異常斋否,以提醒開發(fā)者對(duì)相應(yīng)屬性進(jìn)行設(shè)置。

使用 @Resource拭荤、@Autowired@Qualifier 指定 Bean 的自動(dòng)裝配策略

自動(dòng)裝配是指茵臭,Spring 在裝配 Bean 的時(shí)候,根據(jù)指定的自動(dòng)裝配規(guī)則舅世,將某個(gè) Bean 所需要引用類型的 Bean 注入進(jìn)來旦委。<bean> 元素提供了一個(gè)指定自動(dòng)裝配類型的 autowire 屬性,該屬性有如下選項(xiàng):

no -- 顯式指定不使用自動(dòng)裝配歇终。
byName -- 如果存在一個(gè)和當(dāng)前屬性名字一致的 Bean社证,則使用該 Bean 進(jìn)行注入。如果名稱匹配但是類型不匹配评凝,則拋出異常追葡。如果沒有匹配的類型,則什么也不做奕短。
byType -- 如果存在一個(gè)和當(dāng)前屬性類型一致的 Bean ( 相同類型或者子類型 )宜肉,則使用該 Bean 進(jìn)行注入。byType 能夠識(shí)別工廠方法翎碑,即能夠識(shí)別 factory-method 的返回類型谬返。如果存在多個(gè)類型一致的 Bean,則拋出異常日杈。如果沒有匹配的類型遣铝,則什么也不做。
constructor -- 與 byType 類似莉擒,只不過它是針對(duì)構(gòu)造函數(shù)注入而言的酿炸。如果當(dāng)前沒有與構(gòu)造函數(shù)的參數(shù)類型匹配的 Bean,則拋出異常涨冀。使用該種裝配模式時(shí)填硕,優(yōu)先匹配參數(shù)最多的構(gòu)造函數(shù)。
autodetect -- 根據(jù) Bean 的自省機(jī)制決定采用 byType 還是 constructor 進(jìn)行自動(dòng)裝配鹿鳖。如果 Bean 提供了默認(rèn)的構(gòu)造函數(shù)扁眯,則采用 byType;否則采用 constructor 進(jìn)行自動(dòng)裝配翅帜。
當(dāng)使用byType 或者 constructor 類型的自動(dòng)裝配的時(shí)候姻檀,自動(dòng)裝配也支持引用類型的數(shù)組或者使用了泛型的集合,這樣涝滴,Spring 就會(huì)檢查容器中所有類型匹配的 Bean施敢,組成集合或者數(shù)組后執(zhí)行注入周荐。對(duì)于使用了泛型的 Map 類型,如果鍵是 String 類型僵娃,則 Spring 也會(huì)自動(dòng)執(zhí)行裝配,將所有類型匹配的 Bean 作為值腋妙,Bean 的名字作為鍵默怨。

我們可以給 <beans> 增加 default-autowire 屬性,設(shè)置默認(rèn)的自動(dòng)封裝策略骤素。默認(rèn)值為"no"匙睹。如果使用自動(dòng)裝配的同時(shí),也指定了 property 或者 constructor-arg 標(biāo)簽济竹,則顯式指定的值將覆蓋自動(dòng)裝配的值痕檬。目前的自動(dòng)封裝不支持簡單類型,比如基本類型送浊、String梦谜、Class,以及它們的數(shù)組類型袭景。

在按類型匹配的時(shí)候 ( 可能是 byType唁桩、constructor、autodetect)耸棒,同一個(gè)類型可能存在多個(gè) Bean荒澡,如果被注入的屬性是數(shù)組、集合或者 Map与殃,這可能沒有問題单山,但是如果只是簡單的引用類型,則會(huì)拋出異常幅疼。解決方法有如下幾種:

取消該 Bean 的自動(dòng)裝配特性兵睛,使用顯式的注入。我們可能不希望某個(gè) Bean 被當(dāng)作其他 Bean 執(zhí)行自動(dòng)封裝時(shí)的候選對(duì)象航邢,我們可以給該 <bean> 增加 autowire-candidate="false"酝静。(autowire-candidate 屬性和 autowire 屬性相互獨(dú)立,互不相干狼忱。某個(gè) Bean 可以將 autowire-candidate 設(shè)置為 false膨疏,同時(shí)使用 autowire 特性。) 另外钻弄,我們可以設(shè)置 <beans> 的 default-autowire-candidates 屬性佃却,可以在該屬性中指定可以用于自動(dòng)裝配候選 Bean 的匹配模式,比如 default-autowire-candidates="serv,dao"窘俺,這表示所有名字以 serv 或者 dao 結(jié)尾的 Bean 被列為候選饲帅,其他則忽略复凳,相當(dāng)于其他 Bean 都指定為 autowire-candidate="false",此時(shí)可以顯式為 <bean> 指定 autowire-candidate="true"灶泵。在 <bean> 上指定的設(shè)置要覆蓋 <beans> 上指定的設(shè)置育八。
如果在多個(gè)類型相同的 Bean 中有首選的 Bean,那么可以將該 <bean> 的 primary 屬性設(shè)置為 "true" 赦邻,這樣自動(dòng)裝配時(shí)便優(yōu)先使用該 Bean 進(jìn)行裝配髓棋。此時(shí)不能將 autowire-candidate 設(shè)為 false。
如果使用的是 Java 5 以上版本惶洲,可以使用注解進(jìn)行更細(xì)粒度的控制按声。

使用 @Autowired@Qualifier 注解執(zhí)行自動(dòng)裝配

  • 使用 @Autowired 注解進(jìn)行裝配,只能是根據(jù)類型進(jìn)行匹配恬吕。@Autowired 注解可以用于 Setter 方法签则、構(gòu)造函數(shù)、字段铐料,甚至普通方法渐裂,前提是方法必須有至少一個(gè)參數(shù)。@Autowired 可以用于數(shù)組和使用泛型的集合類型余赢。然后 Spring 會(huì)將容器中所有類型符合的 Bean 注入進(jìn)來芯义。@Autowired 標(biāo)注作用于 Map 類型時(shí),如果 Map 的 key 為 String 類型妻柒,則 Spring 會(huì)將容器中所有類型符合 Map 的 value 對(duì)應(yīng)的類型的 Bean 增加進(jìn)來扛拨,用 Bean 的 id 或 name 作為 Map 的 key。

@Autowired 標(biāo)注作用于普通方法時(shí)举塔,會(huì)產(chǎn)生一個(gè)副作用绑警,就是在容器初始化該 Bean 實(shí)例的時(shí)候就會(huì)調(diào)用該方法。當(dāng)然央渣,前提是執(zhí)行了自動(dòng)裝配计盒,對(duì)于不滿足裝配條件的情況,該方法也不會(huì)被執(zhí)行芽丹。

當(dāng)標(biāo)注了 @Autowired 后北启,自動(dòng)注入不能滿足,則會(huì)拋出異常拔第。我們可以給 @Autowired 標(biāo)注增加一個(gè) required=false 屬性咕村,以改變這個(gè)行為。另外蚊俺,每一個(gè)類中只能有一個(gè)構(gòu)造函數(shù)的 @Autowired.required() 屬性為 true懈涛。否則就出問題了。如果用 @Autowired 同時(shí)標(biāo)注了多個(gè)構(gòu)造函數(shù)泳猬,那么批钠,Spring 將采用貪心算法匹配構(gòu)造函數(shù) ( 構(gòu)造函數(shù)最長 )宇植。

** @Autowired 還有一個(gè)作用就是,如果將其標(biāo)注在 BeanFactory 類型埋心、ApplicationContext 類型指郁、ResourceLoader 類型、ApplicationEventPublisher 類型踩窖、MessageSource 類型上坡氯,那么 Spring 會(huì)自動(dòng)注入這些實(shí)現(xiàn)類的實(shí)例,不需要額外的操作洋腮。**

  • 當(dāng)容器中存在多個(gè) Bean 的類型與需要注入的相同時(shí),注入將不能執(zhí)行手形,我們可以給 @Autowired 增加一個(gè)候選值啥供,做法是在 @Autowired 后面增加一個(gè) @Qualifier 標(biāo)注,提供一個(gè) String 類型的值作為候選的 Bean 的名字库糠。舉例如下:
@Autowired(required=false) 
@Qualifier("ppp") 
public void setPerson(person p){}

@Qualifier 甚至可以作用于方法的參數(shù) ( 對(duì)于方法只有一個(gè)參數(shù)的情況伙狐,我們可以將 @Qualifer 標(biāo)注放置在方法聲明上面,但是推薦放置在參數(shù)前面 )瞬欧,舉例如下:

@Autowired(required=false) 
public void sayHello(@Qualifier("ppp")Person p,String name){}

我們可以在配置文件中指定某個(gè) Bean 的 qualifier 名字贷屎,方法如下:

<bean id="person" class="footmark.spring.Person"> 
   <qualifier value="ppp"/> 
</bean>

如果沒有明確指定 Bean 的 qualifier 名字,那么默認(rèn)名字就是 Bean 的名字艘虎。通常唉侄,qualifier 應(yīng)該是有業(yè)務(wù)含義的,例如 "domain"野建,"persistent" 等属划,而不應(yīng)該是類似 "person" 方式。

我們還可以將@Qualifier 標(biāo)注在集合類型上候生,那么所有 qualifier 名字與指定值相同的 Bean 都將被注入進(jìn)來同眯。

最后,配置文件中需要指定每一個(gè)自定義注解的屬性值唯鸭。我們可以使用 <meta> 標(biāo)簽來代替 <qualifier/> 標(biāo)簽须蜗,如果 <meta> 標(biāo)簽和 <qualifier/> 標(biāo)簽同時(shí)出現(xiàn),那么優(yōu)先使用 <qualifier> 標(biāo)簽目溉。如果沒有 <qualifier> 標(biāo)簽明肮,那么會(huì)用 <meta> 提供的鍵值對(duì)來封裝 <qualifier> 標(biāo)簽。示例如下:

<bean class="footmark.HelloWorld"> 
<qualifier type="MovieQualifier"> 
<attribute key="format" value="VHS"/> 
<attribute key="genre" value="Comedy"/> 
</qualifier> 
</bean> 
<bean class="footmark.HelloWorld"> 
<meta key="format" value="DVD"/> 
<meta key="genre" value="Action"/> 
</bean>

@Autowired 注解對(duì)應(yīng)的后處理注冊(cè)與前面相似停做,只需在配置文件中增加如下一行即可:

<context:annotation-config/>

如果 @Autowired 注入的是 BeanFactory晤愧、ApplicationContextResourceLoader 等系統(tǒng)類型蛉腌,那么則不需要 @Qualifier官份,此時(shí)即使提供了 @Qualifier注解只厘,也將會(huì)被忽略;而對(duì)于自定義類型的自動(dòng)裝配舅巷,如果使用了 @Qualifier 注解并且沒有名字與之匹配的 Bean羔味,則自動(dòng)裝配匹配失敗。

使用 JSR-250 中的 @Resource@Qualifier 注解
如果希望根據(jù) name 執(zhí)行自動(dòng)裝配钠右,那么應(yīng)該使用 JSR-250 提供的 @Resource 注解赋元,而不應(yīng)該使用 @Autowired@Qualifier 的組合。

@Resource 使用 byName 的方式執(zhí)行自動(dòng)封裝飒房。@Resource 標(biāo)注可以作用于帶一個(gè)參數(shù)的 Setter 方法搁凸、字段,以及帶一個(gè)參數(shù)的普通方法上狠毯。@Resource 注解有一個(gè) name 屬性护糖,用于指定 Bean 在配置文件中對(duì)應(yīng)的名字。如果沒有指定 name 屬性嚼松,那么默認(rèn)值就是字段或者屬性的名字嫡良。@Resource@Qualifier 的配合雖然仍然成立,但是 @Qualifier 對(duì)于 @Resource 而言献酗,幾乎與 name 屬性等效寝受。

如果 @Resource 沒有指定 name 屬性,那么使用 byName 匹配失敗后罕偎,會(huì)退而使用 byType 繼續(xù)匹配很澄,如果再失敗,則拋出異常锨亏。在沒有為 @Resource 注解顯式指定 name 屬性的前提下痴怨,如果將其標(biāo)注在 BeanFactory 類型、ApplicationContext 類型器予、ResourceLoader 類型浪藻、ApplicationEventPublisher 類型、MessageSource 類型上乾翔,那么 Spring 會(huì)自動(dòng)注入這些實(shí)現(xiàn)類的實(shí)例爱葵,不需要額外的操作。此時(shí) name 屬性不需要指定 ( 或者指定為"")反浓,否則注入失斆日伞;如果使用了 @Qualifier雷则,則該注解將被忽略辆雾。而對(duì)于用戶自定義類型的注入,@Qualifier 和 name 等價(jià)月劈,并且不被忽略度迂。

<bean> 的primaryautowire-candidate 屬性對(duì) @Resource藤乙、@Autowired 仍然有效。

使用 @Configuration@Bean 進(jìn)行 Bean 的聲明
雖然 2.0 版本發(fā)布以來惭墓,Spring 陸續(xù)提供了十多個(gè)注解坛梁,但是提供的這些注解只是為了在某些情況下簡化 XML 的配置,并非要取代 XML 配置方式腊凶。這一點(diǎn)可以從 Spring IoC 容器的初始化類可以看出:ApplicationContext 接口的最常用的實(shí)現(xiàn)類是 ClassPathXmlApplicationContextFileSystemXmlApplicationContext划咐,以及面向 Portlet 的 XmlPortletApplicationContext 和面向 web 的 XmlWebApplicationContext,它們都是面向 XML 的钧萍。Spring 3.0 新增了另外兩個(gè)實(shí)現(xiàn)類:AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext褐缠。從名字便可以看出,它們是為注解而生风瘦,直接依賴于注解作為容器配置信息來源的 IoC 容器初始化類送丰。由于 AnnotationConfigWebApplicationContextAnnotationConfigApplicationContext 的 web 版本,其用法與后者相比幾乎沒有什么差別弛秋,因此本文將以 AnnotationConfigApplicationContext 為例進(jìn)行講解。

AnnotationConfigApplicationContext 搭配上 @Configuration@Bean 注解俐载,自此蟹略,XML 配置方式不再是 Spring IoC 容器的唯一配置方式。兩者在一定范圍內(nèi)存在著競爭的關(guān)系遏佣,但是它們?cè)诖蠖鄶?shù)情況下還是相互協(xié)作的關(guān)系挖炬,兩者的結(jié)合使得 Spring IoC 容器的配置更簡單,更強(qiáng)大状婶。

之前意敛,我們將配置信息集中寫在 XML 中,如今使用注解膛虫,配置信息的載體由 XML 文件轉(zhuǎn)移到了 Java 類中草姻。

我們通常將用于存放配置信息的類的類名以 “Config” 結(jié)尾,比如 AppDaoConfig.java稍刀、AppServiceConfig.java 等等撩独。我們需要在用于指定配置信息的類上加上 @Configuration 注解,以明確指出該類是 Bean 配置的信息源账月。并且 Spring 對(duì)標(biāo)注 Configuration 的類有如下要求:

  • 配置類不能是 final 的综膀;
  • 配置類不能是本地化的,亦即不能將配置類定義在其他類的方法內(nèi)部局齿;
  • 配置類必須有一個(gè)無參構(gòu)造函數(shù)剧劝。
    AnnotationConfigApplicationContext 將配置類中標(biāo)注了 @Bean 的方法的返回值識(shí)別為 Spring Bean,并注冊(cè)到容器中抓歼,受 IoC 容器管理讥此。@Bean 的作用等價(jià)于 XML 配置中的 <bean/> 標(biāo)簽拢锹。示例如下:
@Configuration 
public class BookStoreDaoConfig{ 
   @Bean 
   public UserDao userDao(){ return new UserDaoImpl();} 
   @Bean 
   public BookDao bookDao(){return new BookDaoImpl();} 
}

Spring 在解析到以上文件時(shí),將識(shí)別出標(biāo)注 @Bean 的所有方法暂论,執(zhí)行之面褐,并將方法的返回值 ( 這里是 UserDaoImpl 和 BookDaoImpl 對(duì)象 ) 注冊(cè)到 IoC 容器中。默認(rèn)情況下取胎,Bean 的名字即為方法名展哭。因此,與以上配置等價(jià)的 XML 配置如下:

<bean id=”userDao” class=”bookstore.dao.UserDaoImpl”/> 
<bean id=”bookDao” class=”bookstore.dao.BookDaoImpl”/>

@Bean 具有以下四個(gè)屬性:

name -- 指定一個(gè)或者多個(gè) Bean 的名字闻蛀。這等價(jià)于 XML 配置中 <bean> 的 name 屬性匪傍。
initMethod -- 容器在初始化完 Bean 之后,會(huì)調(diào)用該屬性指定的方法觉痛。這等價(jià)于 XML 配置中 <bean> 的 init-method 屬性役衡。
destroyMethod -- 該屬性與 initMethod 功能相似,在容器銷毀 Bean 之前薪棒,會(huì)調(diào)用該屬性指定的方法手蝎。這等價(jià)于 XML 配置中 <bean> 的 destroy-method 屬性。
autowire -- 指定 Bean 屬性的自動(dòng)裝配策略俐芯,取值是 Autowire 類型的三個(gè)靜態(tài)屬性棵介。Autowire.BY_NAME,Autowire.BY_TYPE吧史,Autowire.NO邮辽。與 XML 配置中的 autowire 屬性的取值相比,這里少了 constructor贸营,這是因?yàn)?constructor 在這里已經(jīng)沒有意義了吨述。
@Bean 沒有直接提供指定作用域的屬性,可以通過 @Scope 來實(shí)現(xiàn)該功能钞脂,關(guān)于 @Scope 的用法已在上文列舉揣云。

下面講解基于注解的容器初始化。AnnotationConfigApplicationContext 提供了三個(gè)構(gòu)造函數(shù)用于初始化容器芳肌。

AnnotationConfigApplicationContext():該構(gòu)造函數(shù)初始化一個(gè)空容器灵再,容器不包含任何 Bean 信息,需要在稍后通過調(diào)用其 register() 方法注冊(cè)配置類亿笤,并調(diào)用 refresh() 方法刷新容器翎迁。
AnnotationConfigApplicationContext(Class<?>... annotatedClasses):這是最常用的構(gòu)造函數(shù),通過將涉及到的配置類傳遞給該構(gòu)造函數(shù)净薛,以實(shí)現(xiàn)將相應(yīng)配置類中的 Bean 自動(dòng)注冊(cè)到容器中汪榔。
AnnotationConfigApplicationContext(String... basePackages):該構(gòu)造函數(shù)會(huì)自動(dòng)掃描以給定的包及其子包下的所有類,并自動(dòng)識(shí)別所有的 Spring Bean,將其注冊(cè)到容器中痴腌。它不但識(shí)別標(biāo)注 @Configuration 的配置類并正確解析雌团,而且同樣能識(shí)別使用 @Repository、@Service士聪、@Controller锦援、@Component 標(biāo)注的類。
除了使用上面第三種類型的構(gòu)造函數(shù)讓容器自動(dòng)掃描 Bean 的配置信息以外剥悟,AnnotationConfigApplicationContext 還提供了 scan() 方法灵寺,其功能與上面也類似,該方法主要用在容器初始化之后動(dòng)態(tài)增加 Bean 至容器中区岗。調(diào)用了該方法以后略板,通常需要立即手動(dòng)調(diào)用 refresh() 刷新容器,以讓變更立即生效慈缔。

需要注意的是叮称,AnnotationConfigApplicationContext 在解析配置類時(shí),會(huì)將配置類自身注冊(cè)為一個(gè) Bean藐鹤,因?yàn)?@Configuration 注解本身定義時(shí)被 @Component 標(biāo)注了瓤檐。因此可以說,一個(gè) @Configuration 同時(shí)也是一個(gè) @Component娱节。大多數(shù)情況下距帅,開發(fā)者用不到該 Bean,并且在理想情況下括堤,該 Bean 應(yīng)該是對(duì)開發(fā)者透明的。@Configuration 的定義如下所示:

@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Component 
public @interface Configuration { 
String value() default ""; 
}

在一般的項(xiàng)目中绍移,為了結(jié)構(gòu)清晰悄窃,通常會(huì)根據(jù)軟件的模塊或者結(jié)構(gòu)定義多個(gè) XML 配置文件,然后再定義一個(gè)入口的配置文件蹂窖,該文件使用 <import/> 將其他的配置文件組織起來轧抗。最后只需將該文件傳給 ClassPathXmlApplicationContext 的構(gòu)造函數(shù)即可。針對(duì)基于注解的配置瞬测,Spring 也提供了類似的功能横媚,只需定義一個(gè)入口配置類,并在該類上使用 @Import 注解引入其他的配置類即可月趟,最后只需要將該入口類傳遞給 AnnotationConfigApplicationContext灯蝴。具體示例如下:

@Configuration 
@Import({BookStoreServiceConfig.class,BookStoreDaoConfig.class}) 
public class BookStoreConfig{ … }

混合使用 XML 與注解進(jìn)行 Bean 的配置

設(shè)計(jì) @Configuration 和 @Bean 的初衷,并不是為了完全取代 XML孝宗,而是為了在 XML 之外多一種可行的選擇穷躁。由于 Spring 自發(fā)布以來,Spring 開發(fā)小組便不斷簡化 XML 配置因妇,使得 XML 配置方式已經(jīng)非常成熟问潭,加上 Spring 2.0 以后出現(xiàn)了一系列命名空間的支持猿诸,使得 XML 配置方式成為了使用簡單、功能強(qiáng)大的 Bean 定義方式狡忙。而且梳虽,XML 配置的一些高級(jí)功能目前還沒有相關(guān)注解能夠直接支持。因此灾茁,在目前的多數(shù)項(xiàng)目中窜觉,要么使用純粹的 XML 配置方式進(jìn)行 Bean 的配置,要么使用以注解為主删顶,XML 為輔的配置方式進(jìn)行 Bean 的配置竖螃。

之所以會(huì)出現(xiàn)兩者共存的情況,主要?dú)w結(jié)為三個(gè)原因:其一逗余,目前絕大多數(shù)采用 Spring 進(jìn)行開發(fā)的項(xiàng)目特咆,幾乎都是基于 XML 配置方式的,Spring 在引入注解的同時(shí)录粱,必須保證注解能夠與 XML 和諧共存腻格,這是前提;其二啥繁,由于注解引入較晚菜职,因此功能也沒有發(fā)展多年的 XML 強(qiáng)大,因此旗闽,對(duì)于復(fù)雜的配置酬核,注解還很難獨(dú)當(dāng)一面,在一段時(shí)間內(nèi)仍然需要 XML 的配合才能解決問題适室。除此之外嫡意,Spring 的 Bean 的配置方式與 Spring 核心模塊之間是解耦的,因此捣辆,改變配置方式對(duì) Spring 的框架自身是透明的蔬螟。Spring 可以通過使用 Bean 后處理器 (BeanPostProcessor) 非常方便的增加對(duì)于注解的支持。這在技術(shù)實(shí)現(xiàn)上非常容易的事情汽畴。

要使用混合配置方式旧巾,首先需要判斷以哪一種配置方式為主。對(duì)這個(gè)問題的不同回答將會(huì)直接影響到實(shí)現(xiàn)的方式忍些。然而大可不必為此傷腦筋鲁猩,因?yàn)椴徽撌且?XML 為主,還是以注解為主罢坝,配置方式都是簡單而且容易理解的绳匀。這里不存在錯(cuò)誤的決定,因?yàn)閮H僅是表現(xiàn)方式不一樣。我們首先假設(shè)以 XML 配置為主的情況疾棵。

對(duì)于已經(jīng)存在的大型項(xiàng)目戈钢,可能初期是以 XML 進(jìn)行 Bean 配置的,后續(xù)逐漸加入了注解的支持是尔,這時(shí)我們只需在 XML 配置文件中將被 @Configuration 標(biāo)注的類定義為普通的 <bean>殉了,同時(shí)注冊(cè)處理注解的 Bean 后處理器即可。示例如下:

// 假設(shè)存在如下的 @Configuration 類:
 package bookstore.config; 
 import bookstore.dao.*; 
 @Configuration 
 public class MyConfig{ 
 @Bean 
    public UserDao userDao(){ 
        return new UserDaoImpl(); 
    } 
 } 

此時(shí)拟枚,只需在 XML 中作如下聲明即可:

 <beans … > 
    ……
    <context:annotation-config /> 
    <bean class=”demo.config.MyConfig”/> 
 </beans>

由于啟用了針對(duì)注解的 Bean 后處理器薪铜,因此在 ApplicationContext 解析到 MyConfig 類時(shí),會(huì)發(fā)現(xiàn)該類標(biāo)注了 @Configuration 注解恩溅,隨后便會(huì)處理該類中標(biāo)注 @Bean 的方法隔箍,將這些方法的返回值注冊(cè)為容器總的 Bean。

對(duì)于以上的方式脚乡,如果存在多個(gè)標(biāo)注了 @Configuration 的類蜒滩,則需要在 XML 文件中逐一列出。另一種方式是使用前面提到的自動(dòng)掃描功能奶稠,配置如下:

<context:component-scan base-package=”bookstore.config” />
如此俯艰,Spring 將掃描所有 demo.config 包及其子包中的類,識(shí)別所有標(biāo)記了 @Component锌订、@Controller竹握、@Service、@Repository 注解的類辆飘,由于 @Configuration 注解本身也用 @Component 標(biāo)注了啦辐,Spring 將能夠識(shí)別出 @Configuration 標(biāo)注類并正確解析之。

對(duì)于以注解為中心的配置方式蜈项,只需使用 @ImportResource 注解引入存在的 XML 即可昧甘,如下所示:

@Configuration 
 @ImportResource(“classpath:/bookstore/config/spring-beans.xml”) 
 public class MyConfig{ 
……
 } 
 // 容器的初始化過程和純粹的以配置為中心的方式一致:
 AnnotationConfigApplicationContext ctx = 
              new AnnotationConfigApplicationContext(MyConfig.class); 

轉(zhuǎn)自: https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-iocannt/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市战得,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庸推,老刑警劉巖常侦,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贬媒,居然都是意外死亡聋亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門际乘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坡倔,“玉大人,你說我怎么就攤上這事∽锼” “怎么了投蝉?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長征堪。 經(jīng)常有香客問我瘩缆,道長,這世上最難降的妖魔是什么佃蚜? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任庸娱,我火速辦了婚禮,結(jié)果婚禮上谐算,老公的妹妹穿的比我還像新娘熟尉。我一直安慰自己循帐,他們只是感情好冷溶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布搀崭。 她就那樣靜靜地躺著破镰,像睡著了一般核偿。 火紅的嫁衣襯著肌膚如雪晋修。 梳的紋絲不亂的頭發(fā)上恒界,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天喂柒,我揣著相機(jī)與錄音踩蔚,去河邊找鬼棚放。 笑死,一個(gè)胖子當(dāng)著我的面吹牛馅闽,可吹牛的內(nèi)容都是我干的飘蚯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼福也,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼局骤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暴凑,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤峦甩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后现喳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凯傲,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年嗦篱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冰单。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灸促,死狀恐怖诫欠,靈堂內(nèi)的尸體忽然破棺而出涵卵,到底是詐尸還是另有隱情,我是刑警寧澤荒叼,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布轿偎,位于F島的核電站,受9級(jí)特大地震影響甩挫,放射性物質(zhì)發(fā)生泄漏贴硫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一伊者、第九天 我趴在偏房一處隱蔽的房頂上張望英遭。 院中可真熱鬧,春花似錦亦渗、人聲如沸挖诸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽多律。三九已至,卻和暖如春搂蜓,著一層夾襖步出監(jiān)牢的瞬間狼荞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工帮碰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留相味,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓殉挽,卻偏偏與公主長得像丰涉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斯碌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器一死,...
    simoscode閱讀 6,703評(píng)論 2 22
  • 1.1 Spring IoC容器和bean簡介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,578評(píng)論 0 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)傻唾,斷路器投慈,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 內(nèi)容過長,core部分分開發(fā)布冠骄,core章節(jié)第二部分點(diǎn)擊:Spring Framework 官方文檔中文版—Cor...
    kopshome閱讀 33,656評(píng)論 7 24
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,778評(píng)論 6 342