裝配Bean
[TOC]
Spring裝配bean的可選方案
裝配:創(chuàng)建應(yīng)用對象之間協(xié)作關(guān)系的行為通常稱為裝配茫经,這這也是依賴注入的本質(zhì)。
Spring裝配bean的三種方案:
- 在XML中進(jìn)行顯示配置
- 在Java中進(jìn)行顯示配置
- 隱式的bean發(fā)現(xiàn)機(jī)制和自動裝配
自動化裝配bean
Spring有兩種角度來實(shí)現(xiàn)自動化裝配
- 組件掃描:Spring會自動發(fā)現(xiàn)應(yīng)用上下文中所創(chuàng)建的bean
- 自動裝配:Spring自動滿足bean之間的依賴
創(chuàng)建可被發(fā)現(xiàn)的bean
CompactDisc.class - CD接口
package soundsystem.compactdisc;
public interface CompactDisc {
void play();
}
SgtPeppers.class - CD接口實(shí)現(xiàn)類:一張專輯唱片
package soundsystem.compactdisc;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
}
}
Component
注解:表明該類會作為組件類萎津,并告知Spring要為這個類創(chuàng)建bean。
除此之外锉屈,組件掃描默認(rèn)是不啟動的颈渊,我們需要顯示配置Spring,從而命令它去尋找Component
類:
通過Java注解的方式開啟組件掃描
CDPlayerConfig.class - 定義Spring裝配規(guī)則
package soundsystem.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"soundsystem"})
public class CDPlayerConfig {
}
在這個項目中雾家,我的分包如下圖所示:

如果沒有其他配置的話绍豁,@ComponentScan 默認(rèn)會掃描與配置類相同的包以及該包下所有的子包,查找?guī)в蠤Component 注解的類唬党,這樣的話驶拱,Spring會自動為其創(chuàng)建bean。
CDPlayerTest.class - 測試文件
package soundsystem.test;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
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;
import soundsystem.compactdisc.CompactDisc;
import soundsystem.config.CDPlayerConfig;
import soundsystem.mediaplayer.MediaPlayer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Rule
public final SystemOutRule log = new SystemOutRule().enableLog();
@Autowired
private MediaPlayer mediaPlayer;
@Autowired
private CompactDisc compactDisc;
@Test
public void cdShouldNotBeNull(){
assertNotNull(compactDisc);
}
@Test
public void play(){
mediaPlayer.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles",log.getLog());
}
}
SpringJUnit4ClassRunner
可以在測試開始的時候自動創(chuàng)建Spring應(yīng)用上下文阴孟,@ContextConfiguration
告訴測試類需要在CDPlayerConfig
中加載配置永丝。
通過XML的方式開啟組件掃描
spring-config.xml
<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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 使用XML開啟組件掃描 -->
<context:component-scan base-package="soundsystem"/>
</beans>
在這個項目中箭养,我的分包如下圖所示:

CDPlayerTest.class - 測試文件
package soundsystem.test;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
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;
import soundsystem.compactdisc.CompactDisc;
import soundsystem.mediaplayer.MediaPlayer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class CDPlayerTest {
@Rule
public final SystemOutRule log = new SystemOutRule().enableLog();
@Autowired
private MediaPlayer mediaPlayer;
@Autowired
private CompactDisc compactDisc;
@Test
public void cdShouldNotBeNull(){
assertNotNull(compactDisc);
}
@Test
public void play(){
mediaPlayer.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles",log.getLog());
}
}
給bean起一個不同的名稱
@Component(value = "lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
// ...
}
@Component(value = "lonelyHeartsClub") : 給bean設(shè)置不同的ID
設(shè)置組件掃描的基礎(chǔ)包
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
// ...
}
@ComponentScan(basePackages = {"soundsystem","video"}) 可以掃描多個基礎(chǔ)包喝检;但是這樣是類型不安全的撼泛。
除了將包設(shè)置為String之外,@ComponentScan 還提供了另一種格式损俭,那就是將其指定為包中的類或者接口:
@ComponentScan(basePackageClasses = {CompactDisc.class, CDPlayer.class})
public class CDPlayerConfig {
}
這些類所在包將會作為組件掃描的基礎(chǔ)包潘酗。
通過為bean添加注解實(shí)現(xiàn)自動裝配
自動裝配:自動裝配就是讓Spring自動滿足bean依賴的一種方法崎脉,在滿足依賴的過程中伯顶,會在Spring應(yīng)用上下文中尋找匹配某個bean需求的其他bean 。為了聲明要進(jìn)行自動匹配灶体,我們可以借助Spring中的 @Autowired
注解 掐暮。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public CDPlayer(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
上段代碼表明:當(dāng)Spring創(chuàng)建CDPlayer bean的時候,會通過構(gòu)造器來進(jìn)行實(shí)例化并傳入一個可設(shè)置給CompactDisc 類型的bean
@Autowired 不僅可以用在構(gòu)造器上樟结,還可以用在屬性的Setter方法(或其他任何方法)上瓢宦。
@Autowired
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
假如有且只有一個bean匹配依賴需求的話,那么這個bean將會被匹配進(jìn)來鱼辙。
如果沒有匹配的bean玫镐,那么在應(yīng)用上下文創(chuàng)建的時候,Spring會拋出一個異常杜跷,為了避免異常的出現(xiàn)矫夷,可以將@Autowired的required屬性設(shè)置為false
@Autowired(required = false)
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
將@Autowired的required屬性設(shè)置為false時口四,Spring會嘗試自動匹配,如果匹配不到相應(yīng)的bean治笨,Spring會將這個bean處于未裝配的狀態(tài)赤嚼。如果你的代碼中沒有進(jìn)行null檢查的話,這個處于未裝配狀態(tài)的屬性可能會出現(xiàn)NullPointerException 等孵。
如果有多個bean都能滿足依賴關(guān)系的話俯萌,Spring將會拋出一個異常上枕,表示沒有明確指定要選擇哪一個bean進(jìn)行自動裝配。
通過Java代碼裝配bean
當(dāng)你需要將第三方庫組件裝配到你的應(yīng)用中棋恼,是沒有辦法在它的類上加@Component和@Autowired注解的,這時候我們就需要顯示的裝配了义起。有兩種可選方案:Java和XML 师崎。
創(chuàng)建配置類
@Configuration
public class CDPlayerConfig {
}
@Configuration 注解表明這個類是一個配置類抡诞,該類應(yīng)該包含在Spring應(yīng)用上下文如何創(chuàng)建bean的細(xì)節(jié)。
聲明簡單的bean
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
@Bean 注解會告訴Spring這個方法將會返回一個對象肴熏,該對象要注冊為Spring應(yīng)用上下文中的bean 顷窒。
默認(rèn)情況下鞋吉,bean的ID與帶有@Bean注解的方法名是一樣的,但是泼诱,你也可以指定一個不同的名字:
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
借助JavaConfig實(shí)現(xiàn)注入
CDPlayerConfig.class - JavaConfig 類
@Configuration
public class CDPlayerConfig {
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
}
看起來赊锚,CompactDisc是通過調(diào)用 sgtPeppers()
得到的舷蒲,但情況并非完全如此,因?yàn)?sgtPeppers()
方法上添加了@Bean注解堤框,Spring 將會攔截所有對它的調(diào)用纵柿,并確保直接返回該方法所創(chuàng)建的bean , 而不是每一次都對其進(jìn)行實(shí)際的調(diào)用资昧。
假設(shè)你引入了其他的CDPlayer的bean:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anthorCDPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
默認(rèn)情況下,SPirng中的bean都是單例的,所以叽唱,Spring會攔截對sgtPeppers()的調(diào)用并確保返回的是Sprring所創(chuàng)建的bean 微宝, 在這里蟋软,bean就是Spirng在調(diào)用sgtPeppers()時所創(chuàng)建的CompactDisc bean 。
還有另一種寫法:
@Configuration
public class CDPlayerConfig {
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
在Spring調(diào)用cdPlayer() 創(chuàng)建 CDPlayer bean 的時候凄敢,會自動裝配一個CompactDisc 到配置方法中湿痢。不管CompactDisc是通過什么方式創(chuàng)建出來的譬重,SPring都會將其傳入到配置方法中,并用來創(chuàng)建CDPlyaer bean
通過XML裝配bean
spring-config.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sgtPeppers" class="soundsystem.compactdisc.SgtPeppers"/>
</beans>
當(dāng)spring發(fā)現(xiàn)<bean...>元素時滩援,會調(diào)用SgtPeppers的默認(rèn)構(gòu)造器來創(chuàng)建bean 狠怨。
借助構(gòu)造器初始化bean
有兩種基本方案可供選擇:
-
<constructor-arg>
元素 - 使用Spring3.0 引入的 c-命名空間
有些事情<constructor-arg>
可以做到邑遏,但是使用c-命名空間卻無法實(shí)現(xiàn)
構(gòu)造器注入bean的引用
使用<constructor-arg>
元素:
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer">
<constructor-arg ref="sgtPeppers"/>
</bean>
使用c-命名空間的方案:
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:compactDisc-ref="sgtPeppers"/>

屬性名以c: 開頭记盒,也就是命名空間的前綴,表示是構(gòu)造器初始化俩檬。 接下來是要裝配的構(gòu)造器參數(shù)名稱碾盟,-ref表正在裝配的是一個bean的引用冰肴,這個bean的名稱是compactDisc(以圖為例)
代替方案如下:
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:_0-ref="sgtPeppers"/>
c:_0-ref 中的 _0 表示參數(shù)的索引 , 使用索引來識別會比使用名稱識別構(gòu)造器參數(shù)更好一些榔组。
將字面量(字符串)注入到構(gòu)造器中
BlankDisc.class
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
this.title = title;
this.artist = artist;
}
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
}
}
spring-config.xml
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
</bean>
<constructor-arg>
標(biāo)簽中的ref屬性表示引用了其他的bean搓扯,而value屬性表明給定的值要以字符串的形式注入到構(gòu)造器中
更為簡單的寫法:
spring-config.xml ———— 通過索引
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles"/>
裝配集合
在裝配bean引用或者字符串的方面包归,<constructor-arg>
和 c-命名空間的功能是相同的公壤,但有一種情況是 <constructor-arg>
能夠?qū)崿F(xiàn),但是c-命名空間不能實(shí)現(xiàn)的 : 那就是 集合的裝配
BlankDisc.class ———— 引入磁道的概念
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
/**
* 磁道
*/
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
for(String track:tracks){
System.out.println("-Track : " +track);
}
}
}
spring-config.xml
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
<value>track4</value>
</list>
</constructor-arg>
</bean>
與之類似的沾鳄,我們也可以使用<ref>
代替 <value>
洞渔, 來實(shí)現(xiàn)bean引用列表的裝配
當(dāng)磁道為set 類型的時候缚态,我們可以使用<set>
而非 <list>
:
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<set>
<value>track1</value>
<value>track2</value>
<value>track3</value>
<value>track4</value>
</set>
</constructor-arg>
</bean>
借助setter初始化bean
CDPlayer.class
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
spring-config.xml
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer">
<property name="compactDisc" ref="sgtPeppers"/>
</bean>
<bean id="sgtPeppers" class="soundsystem.compactdisc.SgtPeppers">
</bean>
Spring中 <constructor-arg>
元素提供了 c-命名空間作為替代方案玫芦,與之類似,Spring提供了更加簡潔的p-命名空間桥帆,作為 <property>
元素的替代方案老虫。
spring-config.xml
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" p:compactDisc-ref="sgtPeppers"/>
將字面值注入到屬性中
BlankDisc.class
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
for (String track:tracks){
System.out.println("- Track : " + track);
}
}
}
spring-config.xml
<bean id="sgtPeppers" class="soundsystem.compactdisc.BlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band"/>
<property name="artist" value="The Beatles"/>
<property name="tracks">
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
</list>
</property>
</bean>
同樣的祈匙,也可以使用p-命名空間的方式:
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles">
<property name="tracks">
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
</list>
</property>
</bean>
我們可以使用util-命名空間的方式來簡化bean:
我們可以把磁道列表轉(zhuǎn)移到blankDisc bean
之外,并將其聲明到單獨(dú)的bean 之中跪帝,如下所示:
<util:list id="tracks">
<value>track1</value>
<value>track2</value>
</util:list>
這樣些阅,我們就可以簡化blankDisc bean的定義了:
<bean id="sgtPeppers" class="soundsystem.compactdisc.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles" p:tracks-ref="tracks">
</bean>

導(dǎo)入和混合配置
在JavaConfig中引用XML配置
我們假設(shè)CDPlayerConfig有一些笨重市埋,需要將blankDisc
從CDPlayerConfig
中拆分出來。定義到它自己的CDConfig
類中:
CDConfig.class
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc(){
return new SgtPeppers();
}
}
CDPlayerConfig.class
@Configuration
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
我們需要將這兩個config類組合到一起雪营,第一種方法是直接在CDPlayerConfig.class
使用@Import注解導(dǎo)入CDConfgi.class
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig{
// ...
}
更高級的辦法是創(chuàng)建一個新的Config衡便,將兩個配置類組合到一起:
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
public class SoundSystemConfig {
}
假設(shè)我們通過XML的方式配置BlankDisc:
spring-config.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="blankDisc" class="soundsystem.compactdisc.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles">
<property name="tracks">
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
</list>
</property>
</bean>
</beans>
使用@ImportResource注解镣陕,加載XML文件
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
@ImportResource({"classpath:spring-context.xml"})
public class SoundSystemConfig {
}
在XML配置中引用JavaConfig
cd-config.xml
<bean id="compactDisc" class="soundsystem.compactdisc.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles">
<property name="tracks">
<list>
<value>track1</value>
<value>track2</value>
<value>track3</value>
</list>
</property>
</bean>
spring-context.xml
<import resource="cd-config.xml"/>
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:_0-ref="compactDisc">
</bean>
<import>
元素可以導(dǎo)入其他的XML配置文件呆抑,但是不可以導(dǎo)入JavaConfig類
所以汁展,我們可以使用下面的方法引用:
<bean class="soundsystem.config.CDConfig"/>
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:_0-ref="compactDisc"/>
小結(jié)
Spring中裝配bean的三種主要方式:自動化配置,基于Java的顯示配置侈咕,以及基于XML的顯示配置