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)
如果存在多個同一型別的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都是單例模式,因此cdPlayer和anotherCDPlayer這倆bean持有相同的SgtPeppers例項。
當然,還有一種更清楚的寫法:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
@Bean
public CDPlayer anotherCDPlayer() {
return new CDPlayer(sgtPeppers());
}
這種情況下,cdPlayer和anotherCDPlayer這倆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>