裝配Bean
[TOC]
Spring裝配bean的可選方案
裝配:創建應用對象之間協作關系的行為通常稱為裝配,這這也是依賴注入的本質。
Spring裝配bean的三種方案:
- 在XML中進行顯示配置
- 在Java中進行顯示配置
- 隱式的bean發現機制和自動裝配
自動化裝配bean
Spring有兩種角度來實現自動化裝配
- 組件掃描:Spring會自動發現應用上下文中所創建的bean
- 自動裝配:Spring自動滿足bean之間的依賴
創建可被發現的bean
CompactDisc.class - CD接口
package soundsystem.compactdisc;
public interface CompactDisc {
void play();
}
SgtPeppers.class - CD接口實現類:一張專輯唱片
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要為這個類創建bean。
除此之外,組件掃描默認是不啟動的,我們需要顯示配置Spring,從而命令它去尋找Component
類:
通過Java注解的方式開啟組件掃描
CDPlayerConfig.class - 定義Spring裝配規則
package soundsystem.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"soundsystem"})
public class CDPlayerConfig {
}
在這個項目中,我的分包如下圖所示:

如果沒有其他配置的話,@ComponentScan 默認會掃描與配置類相同的包以及該包下所有的子包,查找帶有@Component 注解的類,這樣的話,Spring會自動為其創建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
可以在測試開始的時候自動創建Spring應用上下文,@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設置不同的ID
設置組件掃描的基礎包
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
// ...
}
@ComponentScan(basePackages = {"soundsystem","video"}) 可以掃描多個基礎包;但是這樣是類型不安全的。
除了將包設置為String之外,@ComponentScan 還提供了另一種格式,那就是將其指定為包中的類或者接口:
@ComponentScan(basePackageClasses = {CompactDisc.class, CDPlayer.class})
public class CDPlayerConfig {
}
這些類所在包將會作為組件掃描的基礎包。
通過為bean添加注解實現自動裝配
自動裝配:自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean 。為了聲明要進行自動匹配,我們可以借助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();
}
}
上段代碼表明:當Spring創建CDPlayer bean的時候,會通過構造器來進行實例化并傳入一個可設置給CompactDisc 類型的bean
@Autowired 不僅可以用在構造器上,還可以用在屬性的Setter方法(或其他任何方法)上。
@Autowired
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
假如有且只有一個bean匹配依賴需求的話,那么這個bean將會被匹配進來。
如果沒有匹配的bean,那么在應用上下文創建的時候,Spring會拋出一個異常,為了避免異常的出現,可以將@Autowired的required屬性設置為false
@Autowired(required = false)
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
將@Autowired的required屬性設置為false時,Spring會嘗試自動匹配,如果匹配不到相應的bean,Spring會將這個bean處于未裝配的狀態。如果你的代碼中沒有進行null檢查的話,這個處于未裝配狀態的屬性可能會出現NullPointerException 。
如果有多個bean都能滿足依賴關系的話,Spring將會拋出一個異常,表示沒有明確指定要選擇哪一個bean進行自動裝配。
通過Java代碼裝配bean
當你需要將第三方庫組件裝配到你的應用中,是沒有辦法在它的類上加@Component和@Autowired注解的,這時候我們就需要顯示的裝配了。有兩種可選方案:Java和XML 。
創建配置類
@Configuration
public class CDPlayerConfig {
}
@Configuration 注解表明這個類是一個配置類,該類應該包含在Spring應用上下文如何創建bean的細節。
聲明簡單的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應用上下文中的bean 。
默認情況下,bean的ID與帶有@Bean注解的方法名是一樣的,但是,你也可以指定一個不同的名字:
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
借助JavaConfig實現注入
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是通過調用 sgtPeppers()
得到的,但情況并非完全如此,因為 sgtPeppers()
方法上添加了@Bean注解,Spring 將會攔截所有對它的調用,并確保直接返回該方法所創建的bean , 而不是每一次都對其進行實際的調用。
假設你引入了其他的CDPlayer的bean:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anthorCDPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
默認情況下,SPirng中的bean都是單例的,所以,Spring會攔截對sgtPeppers()的調用并確保返回的是Sprring所創建的bean , 在這里,bean就是Spirng在調用sgtPeppers()時所創建的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調用cdPlayer() 創建 CDPlayer bean 的時候,會自動裝配一個CompactDisc 到配置方法中。不管CompactDisc是通過什么方式創建出來的,SPring都會將其傳入到配置方法中,并用來創建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>
當spring發現<bean...>元素時,會調用SgtPeppers的默認構造器來創建bean 。
借助構造器初始化bean
有兩種基本方案可供選擇:
-
<constructor-arg>
元素 - 使用Spring3.0 引入的 c-命名空間
有些事情<constructor-arg>
可以做到,但是使用c-命名空間卻無法實現
構造器注入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: 開頭,也就是命名空間的前綴,表示是構造器初始化。 接下來是要裝配的構造器參數名稱,-ref表正在裝配的是一個bean的引用,這個bean的名稱是compactDisc(以圖為例)
代替方案如下:
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:_0-ref="sgtPeppers"/>
c:_0-ref 中的 _0 表示參數的索引 , 使用索引來識別會比使用名稱識別構造器參數更好一些。
將字面量(字符串)注入到構造器中
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>
標簽中的ref屬性表示引用了其他的bean,而value屬性表明給定的值要以字符串的形式注入到構造器中
更為簡單的寫法:
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>
能夠實現,但是c-命名空間不能實現的 : 那就是 集合的裝配
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>
, 來實現bean引用列表的裝配
當磁道為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:
我們可以把磁道列表轉移到blankDisc bean
之外,并將其聲明到單獨的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>

導入和混合配置
在JavaConfig中引用XML配置
我們假設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注解導入CDConfgi.class
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig{
// ...
}
更高級的辦法是創建一個新的Config,將兩個配置類組合到一起:
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
public class SoundSystemConfig {
}
假設我們通過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>
元素可以導入其他的XML配置文件,但是不可以導入JavaConfig類
所以,我們可以使用下面的方法引用:
<bean class="soundsystem.config.CDConfig"/>
<bean id="cdPlayer" class="soundsystem.mediaplayer.CDPlayer" c:_0-ref="compactDisc"/>
小結
Spring中裝配bean的三種主要方式:自動化配置,基于Java的顯示配置,以及基于XML的顯示配置