讀Spring實戰(第四版)概括—裝配Bean

很久很久以前讀過Spring實戰(第三版),因爲第三版和第四部差異還是特別明顯的,在整體思想上有了比較重大的改變,比如用註解和JavaConfig替換Xml以及現在非常火熱的Springboot在書的最後也有提到。OK,開始看書,書本的第一章講了一下Spring存在的目的(簡化Java開發)和Spring的功能,以及Spring3->Spring4增加了哪些功能,那我就從第二章開始概括本書,以給我自己後面快速回憶使用。

在Spring中,對象無需自己查找或者創建與其所關聯的其他對象。相反容器負責把需要相互協作的對象引用賦予各個對象。創建應用對象之間協作關係的行爲通常被稱爲裝配(wiring),這也是依賴注入(DI)的本質。

1.Spring配置的可選方案

當描述bean如何裝配時,Spring具有非常大的靈活性,它提供了三種主要的裝配機制:

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

我建議儘可能的使用自動配置的機制。顯示配置越少越好。如果必須要使用顯示配置,我推薦使用類型安全並且比XML更加強大的JavaConfig,最後在使用XML配置。

2.自動化裝配Bean

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

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

組件掃描和自動裝配組合一起就能發揮強大的威力,他們能夠將你的顯示配置降低到最小。

實現自動化裝配有兩種方式:(組件掃描默認是不啓動的)

1)通過XML啓用組件掃描

首先在mvc-config.xml中啓動組件掃描功能,並用base-package屬性指定掃描範圍

<context:component-scan base-package="com.wxh.controller"/>

再通過在需要註解的class上添加@Controller@Service、@Repository、@Component等註解,比如:

@Controller
public class UserController {// ......}

2)通過@ComponentScan註解啓用組件掃描

首先在class上使用@ComponentScan啓用組件掃描,例如:

@ComponentScan
public class AppConfig {// ......}

此外:@ComponentScan(basePackages="conf")等同於@ComponentScan("conf"),然後通過在需要註解的class上添加@Controller@Service、@Repository、@Component等註解,例如:

@Controller
public class UserController {// ......}

對於@ComponentScan可以通過basePackages或者basePackageClasses指定掃描範圍,等同於XML註解中的base-package屬性;如果不指定掃描範圍,則默認掃描當前類所在包以及子包的所有類。當然Craig Walls建議使用basePackageClasses因爲如果代碼重構的話這種方式會立馬發現錯誤,下面是basePackageClasses的使用方式(指定基礎掃描類):

@ComponentScan(basePackageClasses={
		UserController.class
		// ......
})
public class AppConfig {}

使用@Autowired可以爲bean實現自動裝配,@Autowired可以使用在構造函數、Setter方法、普通方法和成員變量上。比如下面的用法:

// 用法一:
@Autowired
MessageSend messageSend;
// 用法二:(構造函數也一樣,主要是函數參數的依賴)
@Autowired
public void setMessageSend(MessageSend messageSend) {
	this.messageSend = messageSend;
}

設置@Autowired(required=false)時,Spring嘗試執行自動裝配,但是如果沒有匹配的bean則忽略,但是這種情況故意出現空指針異常NullPointerException。@Autowired註解可以使用@Inject替換,@component可以使用@Named註解替換,後者是源於Java依賴注入規範。

3.通過Java代碼裝配

大部分的場景自動化裝配Bean是滿足要求的,但是在一些特殊場景下自動化裝配Bean是無法滿足要求的,比如說要將第三方的組件裝配到自己的應用中,因爲沒有方法將@component或者@Autowired註解放置在它們的類上。但是你仍然可以採用顯示裝配方式:Java代碼裝配和XML配置。

首先需要創建配置類,創建配置類的關鍵是使用@Configuration註解來表明這個類是一個配置類,該類包涵Spring上下文中如何創建Bean的細節。

聲明一個簡單Bean:在Java的配置類中編寫一個帶有@Bean註解的方法,比如下面:

@Bean
public UserServiceImpl userService(){
	return new UserServiceImpl();
}

默認情況下Bean的ID是方法名,也可以指定Bean的ID:@Bean(name="userService"),如果有依賴可以使用下面這些的方式來實現:

@Bean
public UserServiceImpl userService(){
	return new UserServiceImpl(userDao());
}
@Bean
public UserDaoImpl userDao(){
	return new UserDaoImpl();
}

上面看起來是調用UserDaoImpl(),其實在配置類中Spring會攔截對這個方法的引用,並返回該方法所創建的bean,而不是每次都對其進行實際調用。當然下面這種方式也是可以的,userService()方法需要userDao作爲參數,Spring創建Bean的時候會自動裝配一個UserDaoImpl到方法中(我猜測應該和@Autowired意思差不多,當Spring Context下只有一個UserDaoImpl就可以通過匹配原則進行裝配),這種方式是被推薦的,如果UserDaoImpl不是在本配置類下配置,任然可以正常使用(比如XML默認的組件掃描等)。

@Bean
public UserServiceImpl userService(UserDaoImpl userDao){
	return new UserServiceImpl(userDao);
}

上面我們通過構造函數的方式實現依賴注入(DI),當然我們也可以用一種更好的方式來實現依賴注入,就是用Setter方法注入,如下所示:

@Bean
public UserServiceImpl userService(UserDaoImpl userDao){
	UserServiceImpl userService = new UserServiceImpl();
	userService.setDao(userDao);
	return userService;
}

上面兩種,通過JavaConfig或者自動化裝配Bean在SpringBoot會被大量推薦使用,下面的XML模式的裝配Bean已經不被推薦使用了。

4.通過XML裝配Bean

XML裝配Bean的方式,雖然已經不推薦了,但是還是我們最早使用的一種Bean裝配的方式,還是需要學習的(很多古老的項目還在用,我目前的公司也是)。下面是一個簡單Bean的聲明:

<bean id="myFile" class="com.wxh.config.MyFile"></bean>

<Bean>元素類似於配置類中的@Bean,如果沒有聲明id="myFile",則這個bean的ID則爲myFile#0,以此類推是myFile#1、myFile#2......

藉助構造器(使用構造函數)注入初始化Bean的方式有兩個:

  • <constructor-arg>元素
  • 使用Spring3中引入的c命名空間

如下所示:

<!-- 下面是使用<constructor-arg>元素方式注入Bean -->
<bean id="myFile" class="com.wxh.config.MyFile">
	<constructor-arg ref="fileProperty"></constructor-arg>
</bean>	
<!-- 下面是使用Spring3.0中引入的c-命名空間方式注入Bean -->
<bean id="myFile-c" class="com.wxh.config.MyFile" 
c:fileProperty-ref="fileProperty" c:count="0"></bean>

上面的兩種方式第二張會比較簡單,配置代碼短,但是風格比較詭異,它的一個解讀如下所示:

當然不論是<constructor-arg>元素還是Spring3.0 c-命名空間還可以使用構造器參數位置來注入相應的bean:

<bean id="myFile" class="com.wxh.config.MyFile">
	<constructor-arg index="0" ref="fileProperty"></constructor-arg>
	<constructor-arg index="1" value="0"></constructor-arg>
</bean>	
<bean id="myFile-c" class="com.wxh.config.MyFile" c:_0-ref="fileProperty" c:_1-ref="0"></bean>

上面說的都是將對象的引用注入到依賴於它們的對象之中,有時候我們也需要一個字面量來配置對象。如下所示(構造函數參數分別是id(int)和name(String)):

<constructor-arg value="101"></constructor-arg>
<constructor-arg value="wuxinhui"></constructor-arg>

對於c標籤的使用就不介紹了,個人覺得不如普通的<constructor-arg>元素好用。

有時候,我們需要使用XML對集合的裝配。構造函數如下所示(需要裝配StringList<String>):

public MyFile(String name, List<String> type) {
	this.name = name;
	this.type = type;
}

對於XMl配置,我們可以使用<null/>來傳遞一個空值給構造器

<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg><null/></constructor-arg>
</bean>

不過這種方式在使用的時候容易出現空指針異常,所以用下面的方式更好,使用<list>或者<set>元素來傳入初始化值:

<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg>
		<list>
			<value>txt</value>
			<value>doc</value>
			<value>docx</value>
			<!--... 其他格式-->
		</list>
	</constructor-arg>
</bean>
<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg>
		<set>
                        <value>txt</value>
			<value>doc</value>
			<value>docx</value>
			<!--... 其他格式 -->
		</set>
	</constructor-arg>
</bean>

list(java.util.List)和set(java.util.Set)的區別在於set是沒有重複值,且無序的。其實就是Java中set和list的區別。

對於類屬性的裝配:有構造器注入和Setter注入兩種;一般強依賴屬性使用構造器,可選依賴屬性使用Setter注入。對於使用註解來裝配屬性上面已經闡述過,下面主要說一下Spring XML方式進行屬性的注入,如下所示:

<bean id="f" class="com.wxh.config.MyFile">
	<!-- 屬性注入 -->
	<property name="name" value="ceshi.docx"></property>
	<!-- 其他屬性 -->
</bean>

或者我們也可以使用p標籤來簡化屬性的注入配置(個人比較推薦,語法比c標籤清晰很多),如下:

<bean id="f" class="com.wxh.config.MyFile" p:name="wxk.txt"></bean>

使用util-命令空間中的元素來注入字面量(數組、集合等),下面是Spring util-命名空間中的元素:

元  素

描  述

<util:constant>

引用某個類型的public static域,並將其暴露爲bean

<util:list>

創建一個java.util.List類型的bean,其中包含值和引用

<util:map>

創建一個java.util.Map類型的bean,其中包含值和引用

<util:properties>

創建一個java.util.Properties類型的bean

<util:property-path>

引用一個bean的屬性(或內嵌屬性),並將其暴露爲bean

<util:set>

創建一個java.utilSet類型的bean,其中包含值和引用

使用事例如下所示:

<!-- 靜態變量 -->
<util:constant static-field="com.wxh.config.MyFile.MAX_SIZE" />
<!-- list -->
<util:list id="list" list-class="java.util.LinkedList">
	<value>D-1</value>
	<value>D-2</value>
	<value>D-3</value>
</util:list>
<!-- map -->
<util:map id="map">
	<entry key="數學" value="44" />
	<entry key="語文" value="77" />
</util:map>
<!-- 加載資源文件 -->
<util:properties id="prop" location="classpath:application.property">
</util:properties>
<!-- 暴露指定bean的屬性 -->
<util:property-path path="myfile.name" />
<util:set id="school">
	<value>小學</value>
	<value>中學</value>
	<value>大學</value>
</util:set>

5.導入和混合配置

在混合配置中,@Autowired自動裝配是考慮全局的所有Bean不管XML配置還是JavaConfig註解配置。一般混合配置的使用方式就是:JavaConfig拆分配置、XML拆分配置、JavaConfig配置中引用XML配置、XML中引用JavaConfig配置

5.1.JavaConfig拆分配置

一般如果Bean比較複雜或者邏輯上比較獨立,我們爲了邏輯上更加清晰,可以將一個JavaConfig拆分成多個。使用@Import註解進行導入,如下所示:

/** AppConfiguration.java */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.wxh.service" })
@Import(InitJavaConfig.class)
public class AppConfiguration {
	// 聲明的Bean 。。。。。。
}

/** InitJavaConfig.java */
@Configuration
@ComponentScan(basePackages = { "com.wxh.service" })
public class InitJavaConfig {
	// 聲明的Bean 。。。。。。
}

5.2.XML拆分配置

當如果我們使用XML進行Bean的配置,我們有時候爲了邏輯更加的清晰,會對XML配置進行拆分。我們可以使用<import>元素來引用其他的XML配置,如下所示可以對拆分的XML進行相互引用:(假如我有一個init-config.xml Bean配置文件)

<import resource="init-config.xml"/>

5.3.JavaConfig配置中引用XML配置

對於使用這種模式的還是比較怪異的,早期我在使用Springboot時,由於不太懂使用註解對Bean配置,用過這種奇特的方式。對於JavaConfig配置中引用XML配置,我們可以使用@ImportResource註解實現,如下使用:

@ContextConfiguration
@Component
@ImportResource("classpath:init-config.xml")
public class AppConfig {
	// .....
}

5.4.XML中引用JavaConfig配置

對於也是比較奇葩,我目前還沒遇到過。不過這種引用JavaConfig配置卻更加簡單了,只需要使用<bean>標籤就可以了。使用如下所示:(AppConfig是使用註釋配置的Bean的類)

<bean class="com.test.config.AppConfig"></bean>
總結:只要是拆分的模式,我習慣性的還是會創建一個根配置,這個配置會展現多個裝配類或者XML文件或者混合模式。大致的使用方式如下所示(混合模式),Root XML中配置:
<bean class="com.test.config.AppConfig"></bean>
<import resource="init-config.xml"/>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章