Spring容器負責創(chuàng)建應用程序中的bean并通過DI來協(xié)調這些對象之間的關系,我們需要做的就是告訴Spring要創(chuàng)建哪些bean以及這些bean的依賴情況。
Spring提供了三種主要的裝配機制,選擇哪種方案很大程度上就是個人喜好的問題,你盡可以選擇自己最喜歡的方式。
1.隱式的bean發(fā)現(xiàn)機制和自動裝配
Spring從兩個角度來實現(xiàn)自動化裝配:
- 組件掃描(component scanning):Spring會自動發(fā)現(xiàn)應用上下文中所創(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。不過,組件掃描默認是不啟用的。我們還需要顯式配置一下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默認會掃描與配置類相同的包。因為CDPlayerConfig類位于soundsystem包中,因此Spring將會掃描這個包以及這個包下的所有子包,查找?guī)в蠤Component注解的類。這樣的話,就能發(fā)現(xiàn)CompactDisc,并且會在Spring中自動為其創(chuàng)建一個bean。如果你想要將config類放入一個獨立的包中以方便管理,也可以為@ComponentScan傳入你想要作為掃描基礎包的包名,如:
@ComponentScan("org.xxx.packagename")
或者
@ComponentScan(basePackages={"org.xxx.packagename","org.xxx.otherpackagename"})
來指定多個基礎包。
?為了以后方便重構(有可能改變包名),那么可以采用更加安全的方式
@ComponentScan(basePackageClasses={abc.class, 123.class})
不再使用String類型的名稱來指定包,為basePackageClasses屬性所設置的數(shù)組中包含了類。這些類所在的包將會作為組件掃描的基礎包。
?如果你更傾向于使用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之間的關系不會像上文所列舉的那樣簡單,不同的組件往往相互依賴來完成復雜的任務,所以,我們需要了解一下Spring自動化配置的另外一方面內容,那就是自動裝配。
?通過自動裝配,將一個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();
}
}
構造器上添加了@Autowired注解,這表明當Spring創(chuàng)建CDPlayer bean的時候,會通過這個構造器來進行實例化并且會傳入一個實現(xiàn)了CompactDisc的bean。
?@Autowired注解不僅能夠用在構造器上,還能用在類的任何方法上。比如說,如果CDPlayer有一個insertDisc()方法,那么可以采用如下的注解形式進行自動裝配:
@Autowired
public insertDisc(CompactDisc cd) {
this.cd = cd;
}
如果沒有匹配的bean,那么在應用上下文創(chuàng)建的時候,Spring會拋出異常。為了避免異常的出現(xiàn),你可以將@Autowired的required屬性設置為false:
@Autowired(required=false)
public insertDisc(CompactDisc cd) {
this.cd = cd;
}
將required屬性設置為false時,Spring會嘗試執(zhí)行自動裝配,但是如果沒有匹配的bean的話,Spring將會讓這個bean處于未裝配的狀態(tài)。但是,把required屬性設置為false時,你需要謹慎對待。如果在你的代碼中沒有進行null檢查的話,這個處于未裝配狀態(tài)的屬性有可能會出現(xiàn)NullPointerException。
2.通過Java代碼裝配bean
大部分情況下組件掃描和自動裝配實現(xiàn)Spring的自動化配置是更為推薦的方式,但有時候自動化配置的方案行不通時,比如說,你想要將第三方庫中
的組件裝配到你的應用中,在這種情況下,是沒有辦法在它的類上添加@Component和@Autowired注解的,因此就不能使用自動化裝配的方案了。
讓我們修改一下CDPlayerConfig類:
package soundsystem;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
}
我們去掉了@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應用上下文中的bean。方法體中包含了最終產生bean實例的邏輯。bean的名字默認為方法名,如有需要,可以使用如下方法修改:
@Bean(name="beanId")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
使用java配置類進行裝配同樣要考慮依賴注入的問題,下面就是一種聲明CDPlayer的可行方案:
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
看起來,cdPlayer所依賴的CompactDisc是通過調用sgtPeppers()得到的,但情況并非完全如此。因為sgtPeppers()方法上添加了@Bean注解,Spring將會攔截所有對它的調用,并確保直接返回該方法所創(chuàng)建的bean,而不是每次都對其進行實際的調用,也就是說sgtPeppers仍然是單例的。
?但是上面的寫法有一個不足,那就是如果sgtPeppers()方法沒有定義在CDPlayerConfig中,而是定義在另一個config或xml配置文件中,cdPlayer方法是無法調用的,所以,這種方式并不合理,來看另一種方法:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc ) {
return new CDPlayer(compactDisc );
}
在這里,cdPlayer()方法請求一個CompactDisc作為參數(shù)。當Spring調用cdPlayer()創(chuàng)建CDPlayerbean的時候,它會自動裝配(不管以何種方式)一個CompactDisc到配置方法之中。然后,方法體就可以按照合適的方式來使用它。借助這種技術,cdPlayer()方法也能夠將CompactDisc注入到CDPlayer的構造器中,而且不用明確引用CompactDisc的@Bean方法。
再次強調一遍,帶有@Bean注解的方法可以采用任何必要的Java功能來產生bean實例。
3.通過XML裝配bean
// TODO
4.解決不同配置方式之間的依賴關系
現(xiàn)在,我們已經知道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注解導入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注解導入:
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 {
}