主要內(nèi)容
- Spring的配置方法概覽
- 自動(dòng)裝配bean
- 基于Java配置文件裝配bean
- 控制bean的創(chuàng)建和銷毀
任何一個(gè)成功的應(yīng)用都是由多個(gè)為了實(shí)現(xiàn)某個(gè)業(yè)務(wù)目標(biāo)而相互協(xié)作的組件構(gòu)成的邻眷,這些組件必須相互了解游沿、能夠相互協(xié)作完成工作赘理。例如墨闲,在一個(gè)在線購(gòu)物系統(tǒng)中另凌,訂單管理組件需要與產(chǎn)品管理組件以及信用卡認(rèn)證組件協(xié)作;這些組件還需要跟數(shù)據(jù)庫(kù)組件協(xié)作從而進(jìn)行數(shù)據(jù)庫(kù)讀寫操作兔港。
在Spring應(yīng)用中啥酱,對(duì)象無(wú)需自己負(fù)責(zé)查找或者創(chuàng)建與其關(guān)聯(lián)的其他對(duì)象,由容器負(fù)責(zé)將創(chuàng)建各個(gè)對(duì)象南誊,并創(chuàng)建各個(gè)對(duì)象之間的依賴關(guān)系身诺。例如,一個(gè)訂單管理組件需要使用信用卡認(rèn)證組件弟疆,它不需要自己創(chuàng)建信用卡認(rèn)證組件戚长,只需要定義它需要使用信用卡認(rèn)證組件即可,容器會(huì)創(chuàng)建信用卡認(rèn)證組件然后將該組件的引用注入給訂單管理組件怠苔。
創(chuàng)建各個(gè)對(duì)象之間協(xié)作關(guān)系的行為通常被稱為裝配(wiring)同廉,這就是依賴注入(DI)的本質(zhì)。
2.1 Spring的配置方法概覽
正如在Spring初探一文中提到的柑司,Spring容器負(fù)責(zé)創(chuàng)建應(yīng)用中的bean迫肖,并通過(guò)DI維護(hù)這些bean之間的協(xié)作關(guān)系。作為開發(fā)人員攒驰,你應(yīng)該負(fù)責(zé)告訴Spring容器需要?jiǎng)?chuàng)建哪些bean以及如何將各個(gè)bean裝配到一起蟆湖。Spring提供三種裝配bean的方式:
- 基于XML文件的顯式裝配
- 基于Java文件的顯式裝配
- 隱式bean發(fā)現(xiàn)機(jī)制和自動(dòng)裝配
絕大多數(shù)情況下,開發(fā)人員可以根據(jù)個(gè)人品味選擇這三種裝配方式中的一種玻粪。Spring也支持在同一個(gè)項(xiàng)目中混合使用不同的裝配方式隅津。
我的建議是:盡可能使用自動(dòng)裝配,越少寫顯式的配置文件越好劲室;當(dāng)你必須使用顯式配置時(shí)(例如伦仍,你要配置一個(gè)bean,但是該bean的源碼不是由你維護(hù))很洋,盡可能使用類型安全充蓝、功能更強(qiáng)大的基于Java文件的裝配方式;最后喉磁,在某些情況下只有XML文件中才又你需要使用的名字空間時(shí)谓苟,再選擇使用基于XML文件的裝配方式。
2.2 自動(dòng)裝配bean
Spring通過(guò)兩個(gè)特性實(shí)現(xiàn)自動(dòng)裝配:
- Component scanning——Spring自動(dòng)掃描和創(chuàng)建應(yīng)用上下文中的beans协怒;
- Autowiring——Spring自動(dòng)建立bean之間的依賴關(guān)系涝焙;
這里用一個(gè)例子來(lái)說(shuō)明:假設(shè)你需要實(shí)現(xiàn)一個(gè)音響系統(tǒng),該系統(tǒng)中包含CDPlayer和CompactDisc兩個(gè)組件斤讥,Spring將自動(dòng)發(fā)現(xiàn)這兩個(gè)bean纱皆,并將CompactDisc的引用注入到CDPlayer中湾趾。
2.2.1 創(chuàng)建可發(fā)現(xiàn)的beans
首先創(chuàng)建CD的概念——CompactDisc接口芭商,如下所示:
package com.spring.sample.soundsystem;
public interface CompactDisc {
void play();
}
CompactDisc接口的作用是將CDPlayer與具體的CD實(shí)現(xiàn)解耦合派草,即面向接口編程。這里還需定義一個(gè)具體的CD實(shí)現(xiàn)铛楣,如下所示:
package com.spring.sample.soundsystem;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Perppers' Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
這里最重要的是@Component注解近迁,它告訴Spring需要?jiǎng)?chuàng)建SgtPeppers bean。除此之外簸州,還需要啟動(dòng)自動(dòng)掃描機(jī)制鉴竭,有兩種方法:基于XML配置文件;基于Java配置文件岸浑,代碼如下(二選一):
- 創(chuàng)建soundsystem.xml配置文件
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.sample.soundsystem" />
</beans>
在這個(gè)XML配置文件中搏存,使用<context:component-scan>標(biāo)簽啟動(dòng)Component掃描功能,并可設(shè)置base-package屬性矢洲。
- 創(chuàng)建Java配置文件
package com.spring.sample.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.spring.sample.soundsystem")
public class SoundSystemConfig {
}
在這個(gè)Java配置文件中有兩個(gè)注解值得注意:@Configuration表示這個(gè).java文件是一個(gè)配置文件璧眠;@ComponentScan表示開啟Component掃描,并且可以設(shè)置basePackages屬性——Spring將會(huì)設(shè)置該目錄以及子目錄下所有被@Component注解修飾的類读虏。
- 自動(dòng)配置的另一個(gè)關(guān)鍵注解是@Autowired责静,基于之前的兩個(gè)類和一個(gè)Java配置文件,可以寫個(gè)測(cè)試
package com.spring.sample.soundsystem;
import com.spring.sample.config.SoundSystemConfig;
import org.junit.Assert;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SoundSystemConfig.class)
public class SoundSystemTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
Assert.assertNotNull(cd);
}
}
運(yùn)行測(cè)試盖桥,測(cè)試通過(guò)灾螃,說(shuō)明@Autowired注解起作用了:自動(dòng)將掃描機(jī)制創(chuàng)建的CompactDisc類型的bean注入到SoundSystemTest這個(gè)bean中。
2.2.2 給被掃描的bean命名
在Spring上下文中揩徊,每個(gè)bean都有自己的ID腰鬼。在上一個(gè)小節(jié)的例子中并沒(méi)有提到這一點(diǎn),但Spring在掃描到SgtPeppers這個(gè)組件并創(chuàng)建對(duì)應(yīng)的bean時(shí)塑荒,默認(rèn)給它設(shè)置的ID為sgtPeppers——是的熄赡,這個(gè)ID就是將類名稱的首字母小寫。
如果你需要給某個(gè)類對(duì)應(yīng)的bean一個(gè)特別的名字袜炕,則可以給@Component注解傳入指定的參數(shù)本谜,例如:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
2.2.3 設(shè)置需要掃描的目標(biāo)basepackage
在之前的例子中,我們通過(guò)給@Component注解傳入字符串形式的包路徑偎窘,來(lái)設(shè)置需要掃描指定目錄下的類并為之創(chuàng)建bean乌助。
可以看出,basePackages是復(fù)數(shù)陌知,意味著你可以設(shè)置多個(gè)目標(biāo)目錄他托,例如:
@Configuration
@ComponentScan(basePackages = {"com.spring.sample.soundsystem", "com.spring.sample.video"})
public class SoundSystemConfig {
}
這種字符串形式的表示雖然可以,但是不具備“類型安全”仆葡,因此Spring也提供了更加類型安全的機(jī)制赏参,即通過(guò)類或者接口來(lái)設(shè)置掃描機(jī)制的目標(biāo)目錄志笼,例如:
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}
通過(guò)如上設(shè)置,會(huì)將CDPlayer和DVDPlayer各自所在的目錄作為掃描機(jī)制的目標(biāo)根目錄把篓。
如果應(yīng)用中的對(duì)象是孤立的纫溃,并且互相之間沒(méi)有依賴關(guān)系,例如SgtPeppersbean韧掩,那么這就夠了紊浩。
2.2.4 自動(dòng)裝配bean
簡(jiǎn)單得說(shuō),自動(dòng)裝配的意思是讓Spring從應(yīng)用上下文中找到對(duì)應(yīng)的bean的引用疗锐,并將它們注入到指定的bean坊谁。通過(guò)@Autowired注解可以完成自動(dòng)裝配。
例如滑臊,考慮下面代碼中的CDPlayer類口芍,它的構(gòu)造函數(shù)被@Autowired修飾,表明當(dāng)Spring創(chuàng)建CDPlayer的bean時(shí)雇卷,會(huì)給這個(gè)構(gòu)造函數(shù)傳入一個(gè)CompactDisc的bean對(duì)應(yīng)的引用鬓椭。
package com.spring.sample.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
還有別的實(shí)現(xiàn)方法,例如將@Autowired注解作用在setCompactDisc()方法上:
@Autowired
public void setCd(CompactDisc cd) {
this.cd = cd;
}
或者是其他名字的方法上聋庵,例如:
@Autowired
public void insertCD(CompactDisc cd) {
this.cd = cd;
}
更簡(jiǎn)單的用法是膘融,可以將@Autowired注解直接作用在成員變量之上,例如:
@Autowired
private CompactDisc cd;
只要對(duì)應(yīng)類型的bean有且只有一個(gè)祭玉,則會(huì)自動(dòng)裝配到該屬性上氧映。如果沒(méi)有找到對(duì)應(yīng)的bean,應(yīng)用會(huì)拋出對(duì)應(yīng)的異常脱货,如果想避免拋出這個(gè)異常岛都,則需要設(shè)置@Autowired(required=false)。不過(guò)振峻,在應(yīng)用程序設(shè)計(jì)中臼疫,應(yīng)該謹(jǐn)慎設(shè)置這個(gè)屬性,因?yàn)檫@會(huì)使得你必須面對(duì)NullPointerException的問(wèn)題扣孟。
如果存在多個(gè)同一類型的bean烫堤,則Spring會(huì)拋出異常,表示裝配有歧義凤价,解決辦法有兩個(gè):(1)通過(guò)@Qualifier注解指定需要的bean的ID鸽斟;(2)通過(guò)@Resource注解指定注入特定ID的bean;
2.2.5 驗(yàn)證自動(dòng)配置
通過(guò)下列代碼利诺,可以驗(yàn)證:CompactDisc的bean已經(jīng)注入到CDPlayer的bean中富蓄,同時(shí)在測(cè)試用例中是將CDPlayer的bean注入到當(dāng)前測(cè)試用例。
package com.spring.sample.soundsystem;
import com.spring.sample.config.SoundSystemConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SoundSystemConfig.class)
public class CDPlayerTest {
public final Logger log = LoggerFactory.getLogger(CDPlayerTest.class);
@Autowired
private MediaPlayer player;
@Test
public void playTest() {
player.play();
}
}
2.3 基于Java配置文件裝配bean
Java配置文件不同于其他用于實(shí)現(xiàn)業(yè)務(wù)邏輯的Java代碼慢逾,因此不能將Java配置文件業(yè)務(wù)邏輯代碼混在一起立倍。一般都會(huì)給Java配置文件新建一個(gè)單獨(dú)的package灭红。
2.3.1 創(chuàng)建配置類
實(shí)際上在之前的例子中我們已經(jīng)實(shí)踐過(guò)基于Java的配置文件,看如下代碼:
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}
@Configuration注解表示這個(gè)類是配置類口注,之前我們是通過(guò)@ComponentScan注解實(shí)現(xiàn)bean的自動(dòng)掃描和創(chuàng)建变擒,這里我們重點(diǎn)是學(xué)習(xí)如何顯式創(chuàng)建bean,因此首先將@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
這行代碼去掉疆导。
2.3.2 定義bean
通過(guò)@Bean注解創(chuàng)建一個(gè)Spring bean赁项,該bean的默認(rèn)ID和函數(shù)的方法名相同葛躏,即sgtPeppers澈段。例如:
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
同樣,可以指定bean的ID舰攒,例如:
@Bean(name = "lonelyHeartsClub")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
可以利用Java語(yǔ)言的表達(dá)能力败富,實(shí)現(xiàn)類似工廠模式的代碼如下:
@Bean
public CompactDisc randomBeatlesCD() {
int choice = (int)Math.floor(Math.random() * 4);
if (choice == 0) {
return new SgtPeppers();
} else if (choice == 1) {
return new WhiteAlbum();
} else if (choice == 2) {
return new HardDaysNight();
} else if (choice == 3) {
return new Revolover();
}
}
2.3.3 JavaConfig中的屬性注入
最簡(jiǎn)單的辦法是將被引用的bean的生成函數(shù)傳入到構(gòu)造函數(shù)或者set函數(shù)中,例如:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
看起來(lái)是函數(shù)調(diào)用摩窃,實(shí)際上不是:由于sgtPeppers()方法被@Bean注解修飾兽叮,所以Spring會(huì)攔截這個(gè)函數(shù)調(diào)用,并返回之前已經(jīng)創(chuàng)建好的bean——確保該SgtPeppers bean為單例猾愿。
假如有下列代碼:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
如果把sgtPeppers()方法當(dāng)作普通Java方法對(duì)待鹦聪,則cdPlayerbean和anotherCDPlayerbean會(huì)持有不同的SgtPeppers實(shí)例——結(jié)合CDPlayer的業(yè)務(wù)場(chǎng)景看:就相當(dāng)于將一片CD同時(shí)裝入兩個(gè)CD播放機(jī)中,顯然這不可能蒂秘。
默認(rèn)情況下泽本,Spring中所有的bean都是單例模式,因此cdPlayer和anotherCDPlayer這倆bean持有相同的SgtPeppers實(shí)例姻僧。
當(dāng)然规丽,還有一種更清楚的寫法:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
這種情況下,cdPlayer和anotherCDPlayer這倆bean持有相同的SgtPeppers實(shí)例撇贺,該實(shí)例的ID為lonelyHeartsClub赌莺。這種方法最值得使用,因?yàn)樗灰驝ompactDisc bean在同一個(gè)配置文件中定義——只要在應(yīng)用上下文容器中即可(不管是基于自動(dòng)掃描發(fā)現(xiàn)還是基于XML配置文件定義)松嘶。
2.4 基于XML配置文件裝配bean
這種是Spring中最原始的定義方式艘狭,在此不再詳述。
2.5 混合使用多種配置方法
通常翠订,可能在一個(gè)Spring項(xiàng)目中同時(shí)使用自動(dòng)配置和顯式配置巢音,而且,即使你更喜歡JavaConfig蕴轨,也有很多場(chǎng)景下更適合使用XML配置港谊。幸運(yùn)的是,這些配置方法可以混合使用橙弱。
首先明確一點(diǎn):對(duì)于自動(dòng)配置歧寺,它從整個(gè)容器上下文中查找合適的bean燥狰,無(wú)論這個(gè)bean是來(lái)自JavaConfig還是XML配置。
2.5.1 在JavaConfig中解析XML配置
- 通過(guò)@Import注解導(dǎo)入其他的JavaConfig斜筐,并且支持同時(shí)導(dǎo)入多個(gè)配置文件龙致;
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
}
- 通過(guò)@ImportResource注解導(dǎo)入XML配置文件;
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath: cd-config.xml")
public class SoundSystemConfig {
}
2.5.2 在XML配置文件中應(yīng)用JavaConfig
- 通過(guò)<import>標(biāo)簽引入其他的XML配置文件顷链;
- 通過(guò)<bean>標(biāo)簽導(dǎo)入Java配置文件到XML配置文件目代,例如
<bean class="soundsystem.CDConfig" />
通常的做法是:無(wú)論使用JavaConfig或者XML裝配,都要?jiǎng)?chuàng)建一個(gè)root configuration嗤练,即模塊化配置定義榛了;并且在這個(gè)配置文件中開啟自動(dòng)掃描機(jī)制:<context:component-scan>
或者@ComponentScan
。
2.6 總結(jié)
這一章中學(xué)習(xí)了Spring 裝配bean的三種方式:自動(dòng)裝配煞抬、基于Java文件裝配和基于XML文件裝配霜大。
由于自動(dòng)裝配幾乎不需要手動(dòng)定義bean,建議優(yōu)先選擇自動(dòng)裝配革答;如何必須使用顯式配置战坤,則優(yōu)先選擇基于Java文件裝配這種方式,因?yàn)橄啾扔赬ML文件残拐,Java文件具備更多的能力途茫、類型安全等特點(diǎn);但是也有一種情況必須使用XML配置文件溪食,即你需要使用某個(gè)名字空間(name space)囊卜,該名字空間只在XML文件中可以使用。
參考資料
本號(hào)專注于后端技術(shù)眠菇、JVM問(wèn)題排查和優(yōu)化边败、Java面試題、個(gè)人成長(zhǎng)和自我管理等主題捎废,為讀者提供一線開發(fā)者的工作和成長(zhǎng)經(jīng)驗(yàn)笑窜,期待你能在這里有所收獲。