1. 程式人生 > >(二)《Spring實戰》——Spring核心

(二)《Spring實戰》——Spring核心

gpo fin 接受 控制臺 應用程序 xsd 為我 Coding 3.2

第二章:裝配Bean

技術分享圖片

在Spring中,對象無需自己查找或創建與其所關聯的其他對象。相反,容器負責把需要相互協作的對象引用賦予各個對象。例如,一個訂單管理組件需要信用卡認證組件,但它不需要自己創建信用卡認證組件。訂單管理組件只需要表明自己兩手空空,容器就會主動賦予它一個信用卡認證組件。

創建應用對象之間協作關系的行為通常稱為裝配(wiring),這也是依賴註入(DI)的本質。

1. Spring配置的可選方案

  Spring容器負責創建應用程序中的bean並通過DI來協調這些對象之間的關系。但是,作為開發人員,你需要告訴Spring要創建哪些bean並且如何將其裝配在一起。當描述bean如何進行裝配時,Spring具有非常大的靈活性,它提供了三種主要的裝配機制

  • 在XML中進行顯式配置。
  • 在Java中進行顯式配置。
  • 隱式的bean發現機制和自動裝配。

Spring有多種可選方案來配置bean,這是非常棒的,但有時候你必須要在其中做出選擇。

  這方面,並沒有唯一的正確答案。你所做出的選擇必須要適合你和你的項目。而且,誰說我們只能選擇其中的一種方案呢?Spring的配置風格是可以互相搭配的,所以你可以選擇使用XML裝配一些bean,使用Spring基於Java的配置(JavaConfig)來裝配另一些bean,而將剩余的bean讓Spring去自動發現。

  即便如此,我的建議是盡可能地使用自動配置的機制。顯式配置越少越好。當你必須要顯式配置bean的時候(比如,有些源碼不是由你來維護的,而當你需要為這些代碼配置bean的時候),我推薦使用類型安全並且比XML更加強大的JavaConfig。最後,只有當你想要使用便利的XML命名空間,並且在JavaConfig中沒有同樣的實現時,才應該使XML。

2. 自動化裝配bean

  盡管顯式裝配技術(借助Java和XML來進行Spring裝配)非常有用,但是在便利性方面,最強大的還是Spring的自動化配置。

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

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

  為了闡述組件掃描和裝配,我們需要創建幾個bean,它們代表了一個音響系統中的組件。首先,要創建CompactDisc 類,Spring會發現它並將其創建為一個bean。然後,會創建一個CDPlayer

類,讓Spring發現它,並將CompactDisc bean註入進來。

2.1 創建可被發現的bean

CompactDisc接口在Java中定義了CD的概念

package soundsystem;

public interface CompactDisc {
  void play();
}

  CompactDisc 的具體內容並不重要,重要的是你將其定義為一個接口。作為接口,它定義了CD播放器對一盤CD所能進行的操作。它將CD播放器的任意實現與CD本身的耦合降低到了最小的程度。

帶有@Component註解的CompactDisc實現類SgtPeppers

package soundsystem;
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";
  
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
}

  和CompactDisc 接口一樣,SgtPeppers 的具體內容並不重要。你需要註意的就是SgtPeppers 類上使用了@Component 註解。這個簡單的註解表明該類會作為組件類,並告知Spring要為這個類創建bean。沒有必要顯式配置SgtPeppers bean,因為這個類使用了@Component 註解,所以Spring會為你把事情處理妥當。

  不過,組件掃描默認是不啟用的。我們還需要顯式配置一下Spring,從而命令它去尋找帶有@Component 註解的類,並為其創建bean。

@ComponentScan註解啟用了組件掃描

package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

  如果沒有其他配置的話,@ComponentScan 默認會掃描與配置類相同的包。因為CDPlayerConfig 類位於soundsystem 包中,因此Spring將會掃描這個包以及這個包下的所有子包,查找帶有@Component 註解的類。這樣的話,就能發現CompactDisc ,並且會在Spring中自動為其創建一個bean。

  如果你更傾向於使用XML來啟用組件掃描的話,那麽可以使用Spring context 命名空間的<context:component-scan> 元素。

通過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:context="http://www.springframework.org/schema/context"
  xmlns:c="http://www.springframework.org/schema/c"
  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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="soundsystem" />

</beans>

測試組件掃描能夠發現CompactDisc

package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
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 cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }

}

  CDPlayerTest 使用了Spring的SpringJUnit4ClassRunner ,以便在測試開始的時候自動創建Spring的應用上下文。註解@ContextConfiguration 會告訴它需要在CDPlayerConfig 中加載配置。因為CDPlayerConfig 類中包含了@ComponentScan ,因此最終的應用上下文中應該包含CompactDisc bean。

  測試代碼中有一個CompactDisc 類型的屬性,並且這個屬性帶有@Autowired 註解,以便於將CompactDisc bean註入到測試代碼之中。

2.2 為組件掃描的bean命名

  Spring應用上下文中所有的bean都會給定一個ID。在前面的例子中,盡管我們沒有明確地為SgtPeppers bean設置ID,但Spring會根據類名為其指定一個ID。具體來講,這個bean所給定的ID為sgtPeppers ,也就是將類名的第一個字母變為小寫。

  如果想為這個bean設置不同的ID,你所要做的就是將期望的ID作為值傳遞給@Component 註解。

技術分享圖片

  還有一種方式是使用Java依賴註入規範(Java Dependency Injection)中所提供的@Named 註解來為bean設置ID。(不常用)

技術分享圖片

2.3 設置組件掃描的基礎包

  為了指定不同的基礎包,你所需要做的就是在@ComponentScan 的value屬性中指明包的名稱:

技術分享圖片

  要表明你所設置的是基礎包,那麽你可以通過basePackages屬性進行配置:

技術分享圖片

  要設置多個基礎包的話,只需要將basePackages 屬性設置為要掃描包的一個數組即可:

技術分享圖片

  除了將包設置為簡單的String類型(缺點類型不安全【not type-safe】)之外,@ComponentScan 還提供了另外一種方法,那就是將其指定為包中所包含的類或接口:

技術分享圖片

  可以看到,basePackages 屬性被替換成了basePackageClasses 。同時,我們不是再使用String 類型的名稱來指定包,為basePackageClasses 屬性所設置的數組中包含了類。這些類所在的包將會作為組件掃描的基礎包。

2.4 通過為bean添加註解實現自動裝配

  簡單來說,自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean。為了聲明要進行自動裝配,我們可以借助Spring的@Autowired 註解。

  如下,構造器上添加了@Autowired 註解,這表明當Spring創建CDPlayer bean的時候,會通過這個構造器來進行實例化並且會傳入一個可設置給CompactDisc 類型的bean。

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

@Component
public class CDPlayer implements MediaPlayer {
  private CompactDisc cd;

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

  public void play() {
    cd.play();
  }
}

  @Autowired 註解不僅能夠用在構造器上,還能用在屬性的Setter方法上。

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

  @Autowired 註解可以用在類的任何方法上。

@Autowired
public insertCompactDisc(CompactDisc cd) {
    this.cd = cd;
}

  不管是構造器、Setter方法還是其他的方法,Spring都會嘗試滿足方法參數上所聲明的依賴。假如有且只有一個bean匹配依賴需求的話,那麽這個bean將會被裝配進來。

  如果沒有匹配的bean,那麽在應用上下文創建的時候,Spring會拋出一個異常。為了避免異常的出現,你可以將@Autowiredrequired 屬性設置為false

@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
    this.cd = cd;
}

  將required 屬性設置為false 時,Spring會嘗試執行自動裝配,但是如果沒有匹配的bean的話,Spring將會讓這個bean處於未裝配的狀態。但是,把required 屬性設置為false 時,你需要謹慎對待。如果在你的代碼中沒有進行null檢查的話,這個處於未裝配狀態的屬性有可能會出現NullPointerException

  @AutowiredSpring特有的註解。如果你不願意在代碼中到處使用Spring的特定註解來完成自動裝配任務的話,那麽你可以考慮將其替換為@Inject

技術分享圖片

  @Inject 註解來源於Java依賴註入規範,該規範同時還為我們定義了@Named 註解。在自動裝配中,Spring同時支持@Inject@Autowired 。盡管@Inject@Autowired 之間有著一些細微的差別,但是在大多數場景下,它們都是可以互相替換的。

2.5 驗證自動裝配

package soundsystem;

import static org.junit.Assert.*;

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;

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

  //測試代碼使用StandardOutputStreamLog 基於控制臺的輸出編寫斷言
  @Rule
  public final StandardOutputStreamLog log = new StandardOutputStreamLog();

  //將CDPlayer bean註入到測試代碼的player 成員變量之中(它是更為通用的MediaPlayer 類型)
  @Autowired
  private MediaPlayer player;
  
  @Autowired
  private CompactDisc cd;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }

  @Test
  public void play() {
    player.play();
    assertEquals(
        "Playing Sgt. Pepper‘s Lonely Hearts Club Band by The Beatles\n", 
        log.getLog());
  }

}

3. 通過Java代碼裝配bean

  在進行顯式配置時,JavaConfig是更好的方案,因為它更為強大、類型安全並且對重構友好。因為它就是Java代碼,就像應用程序中的其他Java代碼一樣。

  同時,JavaConfig與其他的Java代碼又有所區別,在概念上,它與應用程序中的業務邏輯和領域代碼是不同的。盡管它與其他的組件一樣都使用相同的語言進行表述,但JavaConfig是配置代碼。這意味著它不應該包含任何業務邏輯,JavaConfig也不應該侵入到業務邏輯代碼之中。盡管不是必須的,但通常會將JavaConfig放到單獨的包中,使它與其他的應用程序邏輯分離開來,這樣對於它的意圖就不會產生困惑了。

3.1 創建配置類

package soundsystem;import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {
}

  創建JavaConfig類的關鍵在於為其添加@Configuration 註解,@Configuration 註解表明這個類是一個配置類,該類應該包含在Spring應用上下文中如何創建bean的細節。

3.2 聲明簡單的bean

  要在JavaConfig中聲明bean ,我們需要編寫一個方法,這個方法會創建所需類型的實例,然後給這個方法添加@Bean 註解。

 @Bean
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }

  默認情況下,bean的ID與帶有@Bean 註解的方法名是一樣的。當然也可通過name屬性自定義。

 @Bean(name="lonelyHeartsClubBand")
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }

3.3 借助JavaConfig實現註入

  可以使用上面的代碼,通過調用方法來引用bean,但最常用的方法是下面這種:

@Bean
  public CDPlayer cdPlayer(CompactDisc compactDisc) {
    return new CDPlayer(compactDisc);
  }

  在這裏,cdPlayer() 方法請求一個CompactDisc 作為參數。當Spring調用cdPlayer() 創建CDPlayer bean的時候,它會自動裝配一個CompactDisc 到配置方法之中。然後,方法體就可以按照合適的方式來使用它。借助這種技術,cdPlayer() 方法也能夠將CompactDisc 註入到CDPlayer 的構造器中,而且不用明確引用CompactDisc@Bean 方法。

4. 通過xml裝配bean

4.1 創建xml配置規範

  創建新的配置規範。在使用JavaConfig的時候,這意味著要創建一個帶有@Configuration 註解的類,而在XML配置中,這意味著要創建一個XML文件,並且要以<beans> 元素為根。

<?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:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

4.2 聲明一個簡單的bean

<bean id="compactDisc"  class="soundsystem.SgtPeppers" />

4.3 借助構造器註入初始化bean

構造器註入,有兩種基本的配置方案可供選擇:

  • <constructor-arg> 元素
  • 使用Spring 3.0所引入的c-命名空間

4.3.1 構造器註入bean引用

<constructor-arg >使用ref 來引入其他bean

    技術分享圖片

c-命名空間

     技術分享圖片

4.3.2 將字面量註入到構造器中

<constructor-arg>使用value 屬性表明給定的值要以字面量的形式註入到構造器之中。

技術分享圖片

c-命名空間

  ①引用構造器參數的名字

   技術分享圖片

  ②通過參數索引裝配相同的字面量值

   技術分享圖片

4.3.3 裝配集合

 技術分享圖片

4.4 設置屬性

引用bean

<property >通過ref引用bean

 技術分享圖片

p-命名空間

 技術分享圖片

將字面量註入到屬性中

<property> 使用value 屬性設置屬性值

 技術分享圖片

p-命名空間

 技術分享圖片

不能使用p-命名空間來裝配集合,裝配集合可使用<util:list>

 技術分享圖片

現在,我們能夠像使用其他的bean那樣,將磁道列表bean註入到BlankDisc bean的tracks 屬性中

 技術分享圖片

5. 導入和混合配置

  在自動裝配時,它並不在意要裝配的bean來自哪裏。自動裝配的時候會考慮到Spring容器中所有的bean,不管它是在JavaConfig或XML中聲明的還是通過組件掃描獲取到的。

5.1 在JavaConfig中引用XML配置

  假設BlankDisc 定義在名為cd-config.xml 的文件中,該文件位於根類路徑下,那麽可以修改SoundSystemConfig ,讓它使用@ImportResource 註解,如下所示:

  技術分享圖片

  兩個bean——配置在JavaConfig中的CDPlayer 以及配置在XML中BlankDisc ——都會被加載到Spring容器之中。因為CDPlayer 中帶有@Bean 註解的方法接受一個CompactDisc 作為參數,因此BlankDisc 將會裝配進來,此時與它是通過XML配置的沒有任何關系。

5.2 在XML配置中引用JavaConfig

  在XML中,我們可以使用<import >元素來拆分XML配置。

  技術分享圖片

(二)《Spring實戰》——Spring核心