1. 程式人生 > >Spring4 實戰筆記(1):裝配bean—依賴注入的本質

Spring4 實戰筆記(1):裝配bean—依賴注入的本質

這種字串形式的表示雖然可以,但是不具備“型別安全”,因此Spring也提供了更加型別安全的機制,即通過類或者介面來設定掃描機制的目標目錄,例如:

@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}


通過如上設定,會將CDPlayer和DVDPlayer各自所在的目錄作為掃描機制的目標根目錄。

PC:可以做一個空標記(marker interface)作為掃描使用--這些類所在的包會作為元件掃描的基礎包(以及其子包)

自動裝配bean

簡單得說,自動裝配的意思是讓Spring從應用上下文中找到對應的bean的引用,並將它們注入到指定的bean。通過@Autowired註解可以完成自動裝配。

例如,考慮下面程式碼中的CDPlayer類,它的建構函式被@Autowired修飾,表明當Spring建立CDPlayer的bean時,會給這個建構函式傳入一個CompactDisc的bean對應的引用。

package com.spring.sample.soundsystem;

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

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

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

還有別的實現方法,例如將@Autowired註解作用在setCompactDisc()方法上:

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

或者是其他名字的方法上,例如:

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

更簡單的用法是,可以將@Autowired註解直接作用在成員變數之上,例如:

@Autowired
private CompactDisc cd;

只要對應型別的bean有且只有一個,則會自動裝配到該屬性上。如果沒有找到對應的bean,應用會丟擲對應的異常,如果想避免丟擲這個異常,則需要設定@Autowired(required=false)

。不過,在應用程式設計中,應該謹慎設定這個屬性,因為這會使得你必須面對NullPointerException的問題。

如果存在多個同一型別的bean,則Spring會丟擲異常,表示裝配有歧義,解決辦法有兩個:(1)通過@Qualifier註解指定需要的bean的ID;(2)通過@Resource註解指定注入特定ID的bean;(3)@Primary


驗證自動配置

通過下列程式碼,可以驗證:CompactDisc的bean已經注入到CDPlayer的bean中,同時在測試用例中是將CDPlayer的bean注入到當前測試用例。

package com.spring.sample.soundsystem;

import com.spring.sample.config.SoundSystemConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
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 = SoundSystemConfig.class)
public class CDPlayerTest {
    public final Logger log = LoggerFactory.getLogger(CDPlayerTest.class);
    @Autowired
    private MediaPlayer player;

    @Test
    public void playTest() {
        player.play();
    }
}

基於Java配置檔案裝配bean

Java配置檔案不同於其他用於實現業務邏輯的Java程式碼,因此不能將Java配置檔案業務邏輯程式碼混在一起。一般都會給Java配置檔案新建一個單獨的package。


建立配置類

實際上在之前的例子中我們已經實踐過基於Java的配置檔案,看如下程式碼:

@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})
public class SoundSystemConfig {
}

@Configuration註解表示這個類是配置類,之前我們是通過@ComponentScan註解實現bean的自動掃描和建立,這裡我們重點是學習如何顯式建立bean,因此首先將@ComponentScan(basePackageClasses = {CDPlayer.class, DVDPlayer.class})這行程式碼去掉。

定義bean

通過@Bean註解建立一個Spring bean,該bean的預設ID和函式的方法名相同,即sgtPeppers。例如:

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

同樣,可以指定bean的ID,例如:

@Bean(name = "lonelyHeartsClub")
public CompactDisc sgtPeppers() {
    return new SgtPeppers();
}

可以利用Java語言的表達能力,實現類似工廠模式的程式碼如下:

@Bean
public CompactDisc randomBeatlesCD() {
    int choice = (int)Math.floor(Math.random() * 4);

    if (choice == 0) {
        return new SgtPeppers();
    } else if (choice == 1) {
        return new WhiteAlbum();
    } else if (choice == 2) {
        return new HardDaysNight();
    } else if (choice == 3) {
        return new Revolover();
    }
}

JavaConfig中的屬性注入

最簡單的辦法是將被引用的bean的生成函式傳入到建構函式或者set函式中,例如:

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

看起來是函式呼叫,實際上不是:由於sgtPeppers()方法被@Bean註解修飾,所以Spring會攔截這個函式呼叫,並返回之前已經建立好的bean——確保該SgtPeppers bean為單例。

假如有下列程式碼:

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

@Bean
public CDPlayer anotherCDPlayer() {
    return new CDPlayer(sgtPeppers());
}

如果把sgtPeppers()方法當作普通Java方法對待,則cdPlayerbean和anotherCDPlayerbean會持有不同的SgtPeppers例項——結合CDPlayer的業務場景看:就相當於將一片CD同時裝入兩個CD播放機中,顯然這不可能。

預設情況下,Spring中所有的bean都是單例模式,因此cdPlayeranotherCDPlayer這倆bean持有相同的SgtPeppers例項。

當然,還有一種更清楚的寫法:

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

@Bean
public CDPlayer anotherCDPlayer() {
    return new CDPlayer(sgtPeppers());
}


這種情況下,cdPlayeranotherCDPlayer這倆bean持有相同的SgtPeppers例項,該例項的ID為lonelyHeartsClub。這種方法最值得使用,因為它不要求CompactDisc bean在同一個配置檔案中定義——只要在應用上下文容器中即可(不管是基於自動掃描發現還是基於XML配置檔案定義)。


基於XML配置檔案裝配bean

這種是Spring中最原始的定義方式,在此不再詳述


混合使用多種配置方法

通常,可能在一個Spring專案中同時使用自動配置和顯式配置,而且,即使你更喜歡JavaConfig,也有很多場景下更適合使用XML配置。幸運的是,這些配置方法可以混合使用。

首先明確一點:對於自動配置,它從整個容器上下文中查詢合適的bean,無論這個bean是來自JavaConfig還是XML配置。


在JavaConfig中解析XML配置

  • 通過@Import註解匯入其他的JavaConfig,並且支援同時匯入多個配置檔案;
  • @Configuration
    @Import({CDPlayerConfig.class, CDConfig.class})
    public class SoundSystemConfig {
    }

  • 通過@ImportResource註解匯入XML配置檔案;
  • @Configuration
    @Import(CDPlayerConfig.class)
    @ImportResource("classpath: cd-config.xml")
    public class SoundSystemConfig {
    }

在XML配置檔案中應用JavaConfig

  • 通過<import>標籤引入其他的XML配置檔案;
  • 通過<bean>標籤匯入Java配置檔案到XML配置檔案,例如
  • <bean class="soundsystem.CDConfig" />

通常的做法是:無論使用JavaConfig或者XML裝配,都要建立一個root configuration,即模組化配置定義;並且在這個配置檔案中開啟自動掃描機制:<context:component-scan>或者@ComponentScan

工程所需的包Maven:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.qiqi.test</groupId>
  <artifactId>springmvc4_test</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <description>spring新版學習</description>

<properties>
 	  	<jdk.version>1.7</jdk.version>
 </properties>
 
 <dependencies>		
	 <dependency>
	    	<groupId>javax.servlet</groupId>
	    	<artifactId>javax.servlet-api</artifactId>
	    	<version>3.1.0</version>
	
	</dependency>
	<dependency>
	    	<groupId>javax.servlet.jsp</groupId>
	    	<artifactId>jsp-api</artifactId>
	    	<version>2.1</version>    
	    </dependency>
    	
    	<dependency>
    		<groupId>junit</groupId>
    		<artifactId>junit</artifactId>
    		<version>4.12</version>
    		<scope>test</scope>
    	</dependency>
    	
  		
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-aop</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
		<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-beans</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-context</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    		<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-test</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-core</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-expression</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-jdbc</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-tx</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-web</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	<dependency>
    		<groupId>org.springframework</groupId>
    		<artifactId>spring-webmvc</artifactId>
    		<version>4.2.5.RELEASE</version>
    	</dependency>
    	
    	<!-- 切面 Aspect -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.2.5.RELEASE</version>
		</dependency>


 </dependencies>
 
 
		  <build>
		 	<pluginManagement>
		 		<plugins>
		 			<plugin>
		 				<groupId>org.apache.maven.plugins</groupId>
		 				<artifactId>maven-compiler-plugin</artifactId>
		 				<configuration>
		 					<source>${jdk.version}</source>
		 					<target>${jdk.version}</target>
		 					<encoding>utf-8</encoding>
		 				</configuration>
					</plugin>
		 		</plugins>
		 	</pluginManagement> 
		  </build>

</project>