## 4.12 基于 Java 的容器配置
### 4.12.1 基本概念:@Configuration 和@Bean
Spring 中新的 Java 配置支持的核心就是@Configuration 注解的類。這些類主要包括 @Bean 注解的方法來為 Spring 的 IoC 容器管理的對象定義實例,配置和初始化邏輯。
使用@Configuration 來注解類表示類可以被 Spring 的 IoC 容器所使用,作為 bean 定義的資源。最簡單的@Configuration 類可以是這樣的:
```
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
```
這和 Spring 的 XML 文件中的`<beans/>`非常類似,上面的 AppConfig 類和下面的代碼是等同的:
```
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
```
正如你看到的,@Bean 注解扮演了和`<bean/>`元素相同的角色。@Bean 注解會在下面 的章節中詳細地討論。首先,我們要來看看使用基于 Java 的配置創建 Spring 容器的各種方 式。
### 4.12.2 使用 AnnotationConfigApplicationContext 實例化 Spring 容器
下面的章節說明了 Spring 的 AnnotationConfigApplicationContext,在 Spring 3.0 中 是 新加 入 的 。這 個 全 能的 ApplicationContext 實 現 類 可 以 接 受不 僅 僅 是 @Configuration 類作為輸入,也可以是普通的@Component 類,還有使用 JSR-330 元數 據注解的類。
當@Configuration 類作為輸入時,@Configuration 類本身作為 bean 被注冊了, 并且類內所有聲明的@Bean 方法也被作為 bean 注冊了。
當@Component 和 JSR-330 類 作為輸 入時, 它們 被注冊 為 bea n,并 且被 假設如 @Autowired 或@Inject 的 DI 元數據在類中需要的地方使用。
#### 4.12.2.1 簡單構造
當實例化 ClassPathXmlApplicationContext 時,以大致相同的方式,當實例化 AnnotationConfigApplicationContext 時,@Configuration 類可能被作為輸入。這 就允許在 Spring 容器中完全可以不使用 XML:
```
public static void main(String[] args) {
ApplicationContext ctx
= new AnnotationConfigApplicationContext(AppConfig. class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
```
正如上面所提到的,AnnotationConfigApplicationContext 不僅僅局限于和 @Configuration 類合作。任意@Component 或 JSR-330 注解的類都可以作為構造方法的輸入。比如:
```
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(MyServiceImpl.class,
Dependency1.class, Dependency2.MyService myService =
ctx.getBean(MyService.class);
myService.doStuff();
}
```
上面假設 MyServiceImpl,Dependency1 和 Dependency2 使用了 Spring 依賴注 入注解,比如@Autowired。
#### 4.12.2.2 使用 `register(Class<?>...)`來編程構建容器
AnnotationConfigApplicationContext 可以使用無參構造方法來實例化,之后使 用 register() 方 法 來 配 置 。 這 個 方 法 當 編 程 構 建 AnnotationConfigApplicationContext 時尤其有用。
```
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new
AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
```
#### 4.12.2.3 使用 scan(String..)開啟組件掃描
有經驗的 Spring 用戶肯定會熟悉下面這個 Spring 的 context:命名空間中的常用 XML 聲明
```
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
```
在上面的示例中,com.acme 包就會被掃描,去查找任意@Component 注解的類,那些 類就會被注冊為 Spring 容器中的 bean。AnnotationConfigApplicationContext 暴露 出 scan(String ...)方法,允許相同的組件掃描功能:
```
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new
AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
```
> 
> 注意
> 記得@Configuration 類是使用@Component 進行元數據注釋的,所以它們是組件掃 描的候選者!在上面的示例中,假設 AppConfig 是聲明在 com.acme 包(或是其中的子 包)中的,那么會在調用 scan()方法時被找到,在調用 refresh()方法時,所有它的@Bean 方法就會被處理并注冊為容器中的 bean。
#### 4.12.2.4 支 持 Web 應 用 的 AnnotationConfigWebApplicationContext
WebApplicationContext 是 AnnotationConfigApplicationContext 的變種,
適用于 AnnotationConfigWebApplicationContext。當配置 Spring 的 Servlet 監聽器 ContextLoaderListener,Spring MVC 的 DispatcherServlet 等時,這個實現類就 可能被用到了。下面的代碼是在 web.xml 中的片段,配置了典型的 Spring MVC 的 Web 應用 程序。注意 contextClass 上下文參數和初始化參數的使用:
```
<web-app>
<!-- 配置ContextLoaderListener使用
AnnotationConfigWebApplicationContext來代替默認的 XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value> org.springframework.web.context.support.AnnotationCon
figWebApplicationContext
</param-value>
</context-param>
<!-- 配置位置必須包含一個或多個逗號或空格分隔的完全限定
@Configuration類。完全限定包也可以指定于組件掃描 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- 普通方式啟動根應用上下文的ContextLoaderListener -->
<listener>
<listener-class> org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 普通方式聲明 Spring MVC 的 DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 配置DispatcherServlet使用
AnnotationConfigWebApplicationContext來代替默認的 XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value> org.springframework.web.context.support.Annotation
ConfigWebApplicationContext
</param-value>
</init-param>
<!--配置位置必須包含一個或多個逗號或空格分隔的完全限定
@Configuration類-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- 映射所有的請求到/main/*中來派發servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/main/*</url-pattern>
</servlet-mapping>
</web-app>
```
### 4.12.3 構成基于 Java 的配置
#### 4.12.3.1 使用@Import 注解
就像 Spring 的 XML 文件中使用的`<import/>`元素幫助模塊化配置一樣,@Import 注 解允許從其它配置類中加載@Bean 的配置:
```
@Configuration
public class ConfigA {
public @Bean A a() { return new A(); }
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
public @Bean B b() { return new B(); }
}
```
現在,當實例化上下文時,不需要指定 ConfigA.class 和 ConfigB.class 了,僅 僅 ConfigB 需要被顯式提供:
```
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(ConfigB.class);
// 現在bean A 和bean B都會是可用的...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
```
這種方式簡化了容器的實例化,僅僅是一個類需要被處理,而不是需要開發人員在構造 時記住很多大量的@Configuration 類。
在引入的@Bean 定義中注入依賴 上面的示例是可行的,但是很簡單。在很多實際場景中,bean 會有依賴其它配置的類的依賴。當使用 XML 時,這本身不是什么問題,因為沒有調用編譯器,而且我們可以僅僅 聲明 ref="someBean" 并且相信 Spring 在 容 器 初 始化 時 可以 完 成。 當 然 ,當 使 用 @Configuration 的類時,Java 編譯器在配置模型上放置約束,對其它 bean 的引用必須 是符合 Java 語法的。
幸運的是,解決這個問題分層簡單。記得@Configuration 類最終是容器中的 bean- 這就是說它們可以像其它 bean 那樣利用@Autowired 注入元數據! 我們來看一個更加真實的語義,有幾個@Configuration 類,每個都依賴聲明在其它類中的 bean:
```
@Configuration
public class ServiceConfig {
private @Autowired AccountRepository accountRepository;
public @Bean TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private @Autowired DataSource dataSource;
public @Bean AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
public @Bean DataSource dataSource() { /* 返回新的數據源 */ }
}
public static void main(String[] args) {
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SystemTestConfig.class);
// 所有的裝配都使用配置的類...
TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456");
}
```
完全限定引入的 bean 便于導航
在上面的場景中,使用@Autowired 工作正常,提供所需的模塊化,但是準確地決定 在哪兒聲明自動裝配的 bean 還是有些含糊。比如,作為開發者來看待 ServiceConfig, 你如何準確知道@Autowired AccountRepository 在哪里聲明的?它沒有顯式地出現 在代碼中,這可能很不錯。要記得 [SpringSource Tool Suite](http://www.springsource.com/products/sts) 提供工具可以生成展示所有對象是 如何裝配起來的圖片-那可能就是你所需要的。而且,你的 Java IDE 可以很容器發現所有的聲明,還有使用的 AccountRepository 類型,也會很快地給你展示出@Bean 方法的位置 和返回的類型。
在這種歧義不被接受和你想有直接從 IDE 中從一個@Configuration 類到另一個導航 的情景中,要考慮自動裝配配置類的本身:
```
@Configuration
public class ServiceConfig {
private @Autowired RepositoryConfig repositoryConfig;
public @Bean TransferService transferService() {
// 通過配置類到@Bean方法的導航!
return new
TransferServiceImpl(repositoryConfig.accountRepository());
}
}
```
在上面的情形中,定義 AccountRepository 是完全明確的。而 ServiceConfig 卻緊緊耦合到 RepositoryConfig 中了;這就需要我們來權衡了。這種緊耦合可以使用 基于接口或抽象基類的@Configuration 類來減輕。考慮下面的代碼:
```
@Configuration
public class ServiceConfig {
private @Autowired RepositoryConfig repositoryConfig;
public @Bean TransferService transferService() {
return new
TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig
{
public @Bean AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})
// 導入具體的配置!
public class SystemTestConfig {
public @Bean DataSource dataSource() { /* 返回數據源 */ }
}
public static void main(String[] args) {
ApplicationContext ctx
= new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
```
現在 ServiceConfig 和 DefaultRepositoryConfig 的耦合就比較松了,并且內 建的 IDE 工具也一直有效:對于開發人員來說會更加簡單地獲取 RepositoryConfig 實現 類的類型層次。以這種方式,導航@Configuration 類和它們的依賴就和普通的基于接口 代碼的導航過程沒有任何區別了。
#### 4.12.3.2 結合 Java 和 XML 配置
Spring 的@Configuration 類并不是完全 100%地支持 Spring XML 替換的。一些基本特 性,比如 Spring XML 的命名空間會保持在一個理想的方式下去配置容器。在 XML 便于使用 或是必須要使用的情況下,你也有另外一個選擇:以“XML 為中心”的方式來實例化容器, 比如,ClassPathXmlApplicationContext,或者以“ Java 為中心”的方式,使用 AnnotationConfigurationApplicationContext 和@ImportResource 注解來引 入需要的 XML。
以“XML 為中心”使用@Configuration 類從一種特定的方式的包含@Configuration 類的 XML 文件啟動 Spring 容器是不錯的。 比如,在使用了 Spring XML 的大型的代碼庫中,根據需要并從已有的 XML 文件中創建 @Configuration 類是很簡單的。在下面,你會發現在這種“XML 為中心”情形下,使用 @Configuration 類的選擇。
以普通的 Spring `<bean/>`元素聲明@Configuration 類
要記得@Configuration 的類最終僅僅是容器中的 bean。在這個示例中,我們創建了 名為 AppConfig 的@Configuration 類,并且將它包含在 system-test-config.xml 文件中作為`<bean/>`的定義。因為開啟了`<context:annotation-config/>`配置,容器 會識別@Configuration 注解,以合適的方式處理聲明在 AppConfig 中@Bean 方法。
```
@Configuration
public class AppConfig {
private @Autowired DataSource dataSource;
public @Bean AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
public @Bean TransferService transferService() {
return new TransferService(accountRepository());
}
}
system-test-config.xml
<beans>
<!-- 開啟處理注解功能,比如@Autowired和@Configuration -->
<context:annotation-config/>
<context:property-placeholder
location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerData Source">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx
= new ClassPathXmlApplicationContext("classpath:/com/acme/system-t est-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
```
> 
> 注意
> 在上面的 system-test-config.xml 文件中,AppConfig 的`<bean/>`定義沒有聲明 id 元素。這么做也是可以接受的,就不必讓其它 bean 去引用它了。同時也就不可能從 容器中通過明確的名稱來獲取它了。同樣地,DataSource bean,通過類型自動裝配,那么明確的 bean id 就不嚴格要求了。
使用`<context:component-scan/>`來檢索@Configuration 類
因為@Configuration 是使用@Component 來元數據注解的,被@Configuration 注解的類是自動作為組件掃描的候選者的。使用上面相同的語義,我們可以重新來定義 system-test-config.xml 文件來利用組件掃描的優點。注意這種情況下,我們不需要 明確地聲明`<context:annotation-config/>`,因為`<context:component-scan/>` 開啟了相同的功能。
```
system-test-config.xml
<beans>
<!-- 選擇并注冊AppConfig作為bean -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder
location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerData Source">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
```
使用了@ImportResource 導入 XML 的@Configuration 類為中心在@Configuration 類作為配置容器主要機制的應用程序中,使用一些 XML 還是必要的。 在這些情況中,僅僅使用@ImportResource 來定義 XML 就可以了。這么來做就實現了“Java 為中心”的方式來配置容器并保持 XML 在最低限度。
```
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
private @Value("${jdbc.url}") String url;
private @Value("${jdbc.username}") String username;
private @Value("${jdbc.password}") String password;
public @Bean DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder
location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx
= new AnnotationConfigApplicationContext(AppConfig. class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
```
- 第一部分 Spring framework 概述
- 第 1 章 Spring Framework 介紹
- 1.1 依賴注入和控制反轉
- 1.2 模塊
- 1.3 使用方案
- 第二部分 Spring 3 的新特性
- 第 2 章 Spring 3.0 的新特性和增強
- 2.1 Java 5
- 2.2 改進的文檔
- 2.3 新的文章和教程
- 2.4 新的模塊組織方式和系統構建方式
- 2.5 新特性概述
- 第 3 章 Spring 3.1 的新特性和增強
- 3.1 新特性概述
- 第三部分 核心技術
- 第 4 章 IoC 容器
- 4.1 Spring IoC 容器和 bean 的介紹
- 4.2 容器概述
- 4.3 Bean 概述
- 4.4 依賴
- 4.5 Bean 的范圍
- 4.6 自定義 bean 的性質
- 4.7 Bean 定義的繼承
- 4.8 容器擴展點
- 4.9 基于注解的容器配置
- 4.10 類路徑掃描和管理的組件
- 4.11 使用 JSR 330 標準注解
- 4.12 基于 Java 的容器配置