Spring容器負(fù)責(zé)創(chuàng)建應(yīng)用程序中的bean并通過DI來協(xié)調(diào)這些對象之間的關(guān)系,我們需要做的就是告訴Spring要創(chuàng)建哪些bean以及這些bean的依賴情況岁经。
Spring提供了三種主要的裝配機制,選擇哪種方案很大程度上就是個人喜好的問題缀壤,你盡可以選擇自己最喜歡的方式纠亚。
1.隱式的bean發(fā)現(xiàn)機制和自動裝配
Spring從兩個角度來實現(xiàn)自動化裝配:
- 組件掃描(component scanning):Spring會自動發(fā)現(xiàn)應(yīng)用上下文中所創(chuàng)建的bean。
- 自動裝配(autowiring):Spring自動滿足bean之間的依賴蒂胞。
?首先,定義一個接口:
package soundsystem;
public interface CompactDisc {
void play{};
}
以及蛤织,CompactDisc實現(xiàn)類SgtPeppers:
package soundsystem;
import org.springframework.stereotype.Component;
@Component("LonelyHeartsClub") // or @Named
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
需要注意的是,SgtPeppers類使用了@Component注解瞳筏,這個簡單的注解表明該類會作為組件類,并告知Spring要為這個類創(chuàng)建一個id為LonelyHeartsClub的bean姚炕。不過,組件掃描默認(rèn)是不啟用的些椒。我們還需要顯式配置一下Spring掸刊,從而命令它去尋找?guī)в蠤Component注解的類免糕,并為其創(chuàng)建bean忧侧。
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
CDPlayerConfig使用了@Configuration注解,表明這是一個spring的配置類松逊,而@ComponentScan注解則能夠在Spring中啟用組件掃描功能。如果沒有其他配置的話经宏,@ComponentScan默認(rèn)會掃描與配置類相同的包驯击。因為CDPlayerConfig類位于soundsystem包中烁兰,因此Spring將會掃描這個包以及這個包下的所有子包徊都,查找?guī)в蠤Component注解的類。這樣的話碟贾,就能發(fā)現(xiàn)CompactDisc,并且會在Spring中自動為其創(chuàng)建一個bean袱耽。如果你想要將config類放入一個獨立的包中以方便管理,也可以為@ComponentScan傳入你想要作為掃描基礎(chǔ)包的包名史翘,如:
@ComponentScan("org.xxx.packagename")
或者
@ComponentScan(basePackages={"org.xxx.packagename","org.xxx.otherpackagename"})
來指定多個基礎(chǔ)包琼讽。
?為了以后方便重構(gòu)(有可能改變包名),那么可以采用更加安全的方式
@ComponentScan(basePackageClasses={abc.class, 123.class})
不再使用String類型的名稱來指定包钻蹬,為basePackageClasses屬性所設(shè)置的數(shù)組中包含了類。這些類所在的包將會作為組件掃描的基礎(chǔ)包问欠。
?如果你更傾向于使用XML來啟用組件掃描的話,那么可以使用Spring context命名空間的<context:component-scan>元素顺献。
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="package" />
顯然注整,在實際的項目開發(fā)中能曾,bean之間的關(guān)系不會像上文所列舉的那樣簡單肿轨,不同的組件往往相互依賴來完成復(fù)雜的任務(wù),所以椒袍,我們需要了解一下Spring自動化配置的另外一方面內(nèi)容,那就是自動裝配槐沼。
?通過自動裝配捌治,將一個CD注入到CDPlayer之中:
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component // or @Named
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired // or @Inject
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
構(gòu)造器上添加了@Autowired注解,這表明當(dāng)Spring創(chuàng)建CDPlayer bean的時候肖油,會通過這個構(gòu)造器來進(jìn)行實例化并且會傳入一個實現(xiàn)了CompactDisc的bean。
?@Autowired注解不僅能夠用在構(gòu)造器上森枪,還能用在類的任何方法上。比如說县袱,如果CDPlayer有一個insertDisc()方法,那么可以采用如下的注解形式進(jìn)行自動裝配:
@Autowired
public insertDisc(CompactDisc cd) {
this.cd = cd;
}
如果沒有匹配的bean筋遭,那么在應(yīng)用上下文創(chuàng)建的時候,Spring會拋出異常漓滔。為了避免異常的出現(xiàn)编饺,你可以將@Autowired的required屬性設(shè)置為false:
@Autowired(required=false)
public insertDisc(CompactDisc cd) {
this.cd = cd;
}
將required屬性設(shè)置為false時响驴,Spring會嘗試執(zhí)行自動裝配,但是如果沒有匹配的bean的話豁鲤,Spring將會讓這個bean處于未裝配的狀態(tài)。但是养距,把required屬性設(shè)置為false時,你需要謹(jǐn)慎對待棍厌。如果在你的代碼中沒有進(jìn)行null檢查的話,這個處于未裝配狀態(tài)的屬性有可能會出現(xiàn)NullPointerException耘纱。
2.通過Java代碼裝配bean
大部分情況下組件掃描和自動裝配實現(xiàn)Spring的自動化配置是更為推薦的方式毕荐,但有時候自動化配置的方案行不通時,比如說憎亚,你想要將第三方庫中
的組件裝配到你的應(yīng)用中第美,在這種情況下,是沒有辦法在它的類上添加@Component和@Autowired注解的扳缕,因此就不能使用自動化裝配的方案了别威。
讓我們修改一下CDPlayerConfig類:
package soundsystem;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
}
我們?nèi)サ袅薂ComponentScan注解省古,意味著spring容器不再自動掃描我們帶有@Component(或@Named)注解的類了衫樊,下面我們將在CDPlayerConfig類中顯式地配置之前創(chuàng)建的類利花。
?我們在config類中加入以下代碼:
package soundsystem;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
@Bean注解會告訴Spring這個方法將會返回一個對象炒事,該對象要注冊為Spring應(yīng)用上下文中的bean蔫慧。方法體中包含了最終產(chǎn)生bean實例的邏輯。bean的名字默認(rèn)為方法名睡扬,如有需要黍析,可以使用如下方法修改:
@Bean(name="beanId")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
使用java配置類進(jìn)行裝配同樣要考慮依賴注入的問題阐枣,下面就是一種聲明CDPlayer的可行方案:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
看起來,cdPlayer所依賴的CompactDisc是通過調(diào)用sgtPeppers()得到的甩鳄,但情況并非完全如此额划。因為sgtPeppers()方法上添加了@Bean注解,Spring將會攔截所有對它的調(diào)用揖赴,并確保直接返回該方法所創(chuàng)建的bean抑胎,而不是每次都對其進(jìn)行實際的調(diào)用,也就是說sgtPeppers仍然是單例的。
?但是上面的寫法有一個不足盆昙,那就是如果sgtPeppers()方法沒有定義在CDPlayerConfig中焊虏,而是定義在另一個config或xml配置文件中,cdPlayer方法是無法調(diào)用的炼团,所以,這種方式并不合理易桃,來看另一種方法:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc ) {
return new CDPlayer(compactDisc );
}
在這里锌俱,cdPlayer()方法請求一個CompactDisc作為參數(shù)。當(dāng)Spring調(diào)用cdPlayer()創(chuàng)建CDPlayerbean的時候造寝,它會自動裝配(不管以何種方式)一個CompactDisc到配置方法之中吭练。然后,方法體就可以按照合適的方式來使用它签赃。借助這種技術(shù)浑侥,cdPlayer()方法也能夠?qū)ompactDisc注入到CDPlayer的構(gòu)造器中寓落,而且不用明確引用CompactDisc的@Bean方法。
再次強調(diào)一遍史飞,帶有@Bean注解的方法可以采用任何必要的Java功能來產(chǎn)生bean實例仰税。
3.通過XML裝配bean
// TODO
4.解決不同配置方式之間的依賴關(guān)系
現(xiàn)在,我們已經(jīng)知道cdPlayer bean需要依賴于compactDisc bean吐绵,假如我們的cdPlayer bean依然由CDPlayerConfig來裝配河绽,而compactDisc bean則改由CDConfig來裝配:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
一種方法就是在CDPlayerConfig中使用@Import注解導(dǎo)入CDConfig:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Bean;
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc ) {
return new CDPlayer(compactDisc );
}
}
或者采用一個更好的辦法耙饰,也就是不在CDPlayerConfig中使用@Import,而是創(chuàng)建一個更高級別的SoundSystemConfig廷痘,在這個類中使用@Import將兩個配置類組合在一起:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
}
同樣的笋额,假如還有一部分bean定義在bean-config.xml中,則需要用@ImportResource注解導(dǎo)入:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
@ImportResource("classpath:bean-config.xml")
public class SoundSystemConfig {
}