Spring入門(二):自動化裝配bean

周偉偉的技術部落格發表於2019-03-06

Spring從兩個角度來實現自動化裝配:

  • 元件掃描(component scanning):Spring會自動發現應用上下文中需要建立的bean。
  • 自動裝配(autowiring):Spring會自動滿足bean之間的依賴。

為了更形象的解釋元件掃描與自動裝配,我們舉一個音響系統的例子,主要包含以下內容:

  • CD介面
  • CD介面的一個實現類
  • CD播放器

關於CD和CD播放器關係的解釋:

如果你不將CD插入(注入)到CD播放器中,那麼CD播放器其實是沒有太大用處的。所以,可以這樣說,

CD播放器依賴於CD才能完成它的使命。

1.建立可被發現的bean

先建立CD介面CompactDisc:

package soundsystem;

public interface CompactDisc {
    void play();
}
複製程式碼

然後建立CD介面的一個實現類SgtPeppers:

package soundsystem;

import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc {

    @Override
    public void play() {
        String title = "Sgt.Pepper's Lonely Hearts Club Band ";
        String artists = "The Beatles";
        System.out.println("Playing " + title + " By " + artists);
    }
}
複製程式碼

SgtPeppers類與以往類的區別在於使用了@Component註解。這個註解表明該類會作為元件類,並告知Spring要為這個類建立bean。

建立了bean,那麼如何讓Spring發現它呢?這時就需要用到元件掃描,不過,在Spring中,元件掃描預設是不啟用的。因此我們需要顯式配置一下Spring,從而命令它去尋找帶有@Component註解的類,併為其建立bean。

建立CDPlayerConfig類:

package soundsystem;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class CDPlayerConfig {
}
複製程式碼

這個類與以往類的區別是使用了@ComponentScan註解,這個註解能夠Spring中啟用元件掃描。

@ComponentScan預設會掃描與配置類相同的包以及這個包下的所有子包,查詢帶有@Component註解的類。

2.驗證元件掃描

為了驗證建立的bean能否被Spring發現,我們建立一個簡單的JUnit測試,完成此測試需要匯入以下兩個jar包:

  • hamcrest-core-2.1.jar
  • junit-4.12.jar

匯入jar包的方式如下:

Spring入門(二):自動化裝配bean

Spring入門(二):自動化裝配bean

Spring入門(二):自動化裝配bean

Spring入門(二):自動化裝配bean

Spring入門(二):自動化裝配bean

匯入完成後的專案結構圖如下所示:

Spring入門(二):自動化裝配bean

package soundsystem;

import static 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 = CDPlayerConfig.class)
public class CDPlayerTest {

    @Autowired
    private CompactDisc compactDisc;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(compactDisc);
        compactDisc.play();
    }
}
複製程式碼

程式碼簡單講解:

@RunWith(SpringJUnit4ClassRunner.class),會在測試開始的時候自動建立Spring的應用上下文。

@ContextConfiguration(classes = CDPlayerConfig.class)會告訴Spring需要在CDPlayerConfig中載入配置。

欄位compactDisc上的@Autowired註解,會將CompactDisc bean(其實是SgtPeppers)注入到測試程式碼之中。

執行測試方法cdShouldNotBeNull,會發現測試通過,compactDisc不為null:

Spring入門(二):自動化裝配bean

3.為元件掃描的bean命名

Spring應用上下文中所有的bean都會給定一個ID,預設情況下,Spring會將類名的第一個字母變為小寫,作為該bean的ID。

如上面程式碼中SgtPeppers bean的ID為sgtPeppers。

有以下兩種方式來設定bean ID:

方式1:使用@Component設定bean ID

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
	...
}
複製程式碼

方式2:使用@Named設定bean ID

@Named註解不是Spring框架的註解,而是Java 依賴注入規範(Java Dependency Injection)中的註解,因此需要匯入jar包:javax.inject-1.jar。

package soundsystem;

import javax.inject.Named;

@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
	...
}

複製程式碼

在Spring專案中建議使用@Component註解。

4.設定元件掃描的基礎包

按照預設規則 ,@ComponentScan註解會以配置類所在的包作為基礎包(base package)來掃描元件。

但有時候,我們會將配置類放在單獨的包中,使其與其他的應用程式碼區分開來。

這種場景下,預設的基礎包就滿足不了需求。

@ComponentScan註解支援傳入指定的基礎包,有以下幾種場景:

4.1 指定要掃描的基礎包(單個)

@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
複製程式碼

或者:

@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {
}
複製程式碼

4.2 指定要掃描的基礎包(多個)

@ComponentScan(basePackages = {"soundsystem", "video"})
public class CDPlayerConfig {
}
複製程式碼

4.3 指定要掃描的基礎包(型別安全)

@ComponentScan(basePackageClasses = {CDPlayer.class})
public class CDPlayerConfig {
}
複製程式碼

如上所示,basePackageClasses也支援指定多個類,指定類所在的包將會作為元件掃描的基礎包。

建議使用這種型別安全方式來指定掃描的基礎包。

5.通過為bean新增註解實現自動裝配

自動裝配是讓Spring自動滿足bean 依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需要的其他bean。

實現自動裝配,需要使用Spring的@Autowired註解。

@Autowired一般情況下,有以下3種使用方式:

5.1 使用在構造器上

package soundsystem;

public interface MediaPlayer {
    void play();
}
複製程式碼
package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc compactDisc;

    @Autowired
    public CDPlayer(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }

    @Override
    public void play() {
        compactDisc.play();
    }
}
複製程式碼

5.2 使用在屬性的Setter方法上

package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc compactDisc;

    @Autowired
    public void setCompactDisc(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }

    @Override
    public void play() {
        compactDisc.play();
    }
}
複製程式碼

5.3 使用在類的任何方法上

package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc compactDisc;

    @Autowired
    public void insertDisc(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }

    @Override
    public void play() {
        compactDisc.play();
    }
}
複製程式碼

不管是構造器、Setter方法還是其他的方法,Spring都會嘗試滿足方法引數上所宣告的依賴。

假如有且只有一個bean匹配依賴需求的話,那麼這個bean將會被裝配進來。

如果沒有匹配的bean,那麼在應用上下文建立的時候,Spring會丟擲一個異常。

可以通過設定require屬性為false避免該異常出現:

@Autowired(required = false)
public CDPlayer(CompactDisc compactDisc) {
    this.compactDisc = compactDisc;
}
複製程式碼

不過建議謹慎使用,避免未找到bean進行匹配,而且程式碼沒有進行null檢查而出現NullPointerException。

如果有多個bean都能滿足依賴關係的話,Spring將會丟擲一個異常,表明沒有明確指定要選擇哪個bean進行自動裝配。

@Autowired註解也可以替換成@Inject註解(來源於Java依賴注入規範),同樣可以實現自動裝配:

package soundsystem;

import org.springframework.stereotype.Component;
import javax.inject.Inject;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc compactDisc;

    @Inject
    public CDPlayer(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }

    ...
}
複製程式碼

在Spring專案中建議使用@Inject註解。

6.驗證自動裝配

修改CDPlayerTest類程式碼測試自動裝配

package soundsystem;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {

    @Rule
    public final StandardOutputStreamLog log = new StandardOutputStreamLog();

    @Autowired
    private MediaPlayer player;

    @Autowired
    private CompactDisc compactDisc;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(compactDisc);
        compactDisc.play();
    }

    @Test
    public void play() {
        player.play();
        assertEquals("Playing Sgt.Pepper's Lonely Hearts Club Band By The Beatles\r\n", log.getLog());
    }
}
複製程式碼

因為程式碼中使用了StandardOutputStreamLog類,因此需要匯入jar包:system-rules-1.16.0.jar

執行測試方法play(),輸出內容和預期一致,說明欄位player已經被MediaPlayer的實現類CDPlayer bean裝配,測試通過,如下所示:

Spring入門(二):自動化裝配bean

7.原始碼地址

github.com/zwwhnly/Spr…,歡迎大家下載,有問題可以多多交流。

相關文章