1. The IoC container
1.1. Introduction to the Spring IoC container and beans
Inversion of control (IoC): 控制反轉.
Dependence injection (DI): 依賴注入, IoC 的同義詞.
org.springframework.beans 和 org.springframework.context 是 Spring IoC 容器的基礎包.
IoC 容器核心接口: BeanFactory, ApplicationContext. 后者繼承自前者且是前者的擴展.
Beans: Spring 概念, 指組成你的應用程序的普通對象, 它們由 IoC 容器初始化, 組裝, 并管理.
Configuration metadata: Spring 概念, 指用來創建 beans 的配置信息, 它也包含 beans 之間的依賴關系.
1.2. Container overview
ApplicationContext 代表了 Spring 的 IoC 容器, 它通過讀取 configuration metadata 的方式來初始化, 配置, 并組裝 beans. Configuration metadata 主要有 3 種形式: XML, Java 注解, Java 代碼.
Spring 自帶了一些開箱即用的 ApplicationContext 實現. 例如想要在 standalone application 中初始化 IoC 容器, 通常情況下你得 new 一個 ClassPathXmlApplicationContext 或者 FileSystemApplicationContext 實例.
在大多數場景下, 都不需要你顯示地去初始化 IoC 容器. 例如在 web 應用程序中, web.xml 中的幾行模板配置就能搞定這一切.
1.2.1. Configuration metadata
你的應用程序由很多 beans 組成, 而 configuration metadata 就是這些 beans 的配置信息. IoC 容器通過讀取這些配置信息來初始化, 配置, 并組裝這些 beans.
XML 格式的 configuration metadata 是最傳統的配置格式. 而 IoC 容器本身是和具體的配置格式解耦的, 你可以自由選擇 configuration metadata 的格式.
Configuration metadata 包含若干的 bean definition 信息. 這些 bean definition 在 XML 配置中就是根元素 <beans/> 中的一個 <bean/> 元素, 在 Java 代碼配置中, 就是 @Configuration 類中的一個 @Bean 方法.
一個 bean definition 就是一個對象的配置信息, 這些對象通常是你應用程序中的基礎組件, 比如說各種 service, dao 之類的. 通常你不會配置領域類 (domain objects) 的 bean definition, 因為這些類通常是由 dao 或者業務代碼創建的.
Configuration metadata 的 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
String 類型的 id 屬性唯一標識一個 bean definition, class 屬性標志一個 bean 的類型, 使用 Java 類的全限定名.
1.2.2. Instantiating a container
直接明了:
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
Composing XML-based configuration metadata
你的應用程序中可能有很多個 bean definition 配置文件, 比如 service 模塊一個, dao 模塊一個. 這種情況下你可以像上面的示例一樣, 把每個配置文件的資源路徑作為參數傳遞給 ApplicationContext 的構造器, 來初始化一個 IoC 容器.
另一種更簡明的方法是在一個根配置文件里使用 <import/> 標簽:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
然后把這個根配置文件的資源路徑傳給 ApplicationContext 的構造器即可. 注意, 這里 <import/> 標簽引入的配置文件的資源路徑, 是根配置文件的相對路徑, 所以這里的資源路徑不建議以 "/" 開頭 (雖然加了也沒影響).
不建議用 "../" 引用根配置文件的上層目錄中的文件.
<import/> 標簽中可以使用絕對路徑, 如 "file:C:/config/services.xml" 或者 "classpath:/config/services.xml". 但是不建議這么做. 如果確實需要, 你可以使用占位符 "${...}" 的形式.
1.2.3. Using the container
你可以像這樣來使用 ApplicationContext:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
對于 Groovy 配置文件:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最靈活的方式是 GenericApplicationContext 加 reader delegates:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
或者:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
如果你愿意, 你可以在同一個 ApplicationContext 中混合使用各種 reader delegates, 來同時加載不同的配置文件.
然后你可以使用 getBean
方法來獲取 beans 的實例. 但是不建議你這么做, 因為這樣就把你的業務代碼和 Spring 框架的 API 耦合起來了. 你應該使用依賴注入的方式來獲取需要的實例.
1.3. Bean overview
一個Spring IoC 容器管理著若干個 beans. 這些 beans 的配置信息是由你提供的, 例如以 xml 配置文件的形式.
在容器內部, 這些 bean 的定義信息由 BeanDefinition 對象來表示, 它包含以下元數據:
- 類的全限定名, 通常是這個 bean 的具體實現類.
- Bean 的行為配置元素, 它們定義一個 bean 在容器中的行為 (scope, lifecycle callbacks 等等).
- 對于其他 bean 的引用信息, 這些引用通常被稱為依賴.
- 其它的一些配置信息, 例如一個管理連接池的 bean, 它的連接數, 連接池大小等等.
這些元數據對應于配置中的屬性如下表所示:
Property | Explained in... |
---|---|
class | Instantiating beans |
name | Naming beans |
scope | Bean scopes |
constructor arguments | Dependency Injection |
properties | Dependency Injection |
autowiring mode | Autowiring collaborators |
lazy-initialization mode | Lazy-initialized beans |
initialization method | Initialization callbacks |
destruction method | Destruction callbacks |
除了通過 bean 定義信息來創建 bean 對象, ApplicationContext
也允許注冊業已存在的對象 (在 IoC 容器以外創建的對象). 你可以通過調用 ApplicationContext
的 getBeanFactory()
方法來獲得 BeanFactory
的實現 DefaultListableBeanFactory
對象. 它的 registerSingleton(..)
和 registerBeanDefinition(..)
方法允許這種注冊操作. 然而這種用法并不常見.
Bean 定義信息的元數據和手工注冊的單例對象應該盡早地提供給 IoC 容器, 以便它在組裝 bean 或者其它的一些內省步驟中可以正確地響應對于這些 bean 的請求. 然而對于已存元數據或者單例對象的覆寫操作, IoC 容器在某種程度上是支持的. 但是這種支持不是官方的, 所以在運行時去注冊新的 beans 有可能引起 concurrent access exception 異常, 或者導致 IoC 容器的狀態錯亂.
1.3.1. Naming beans
每個 bean 都有一個或多個標識信息, 這些標識信息在其宿主容器中必須唯一. 通常一個 bean 只有一個標識信息 (id), 但若需要更多的標識信息, 可以取別名 (aliases).
在 xml 配置文件中, 你可以用 id/name 屬性來指定一個 bean 的標識信息. Id 屬性只允許指定一個標識信息, 通常由字母數字組成 (雖然你可以使用一些特殊的字符). Name 屬性允許你指定若干個標識信息, 標識信息之間用 ,
, ;
或者空格來分隔.
當然你也可以不提供任何的 id 或 name, 這時容器會為這個 bean 分配一個唯一的標識信息. 但是, 如果你想從其他地方引用這個 bean, 你就必須顯式地提供一個 id 或 name.
在開啟 Spring 的路徑掃描功能時, Spring 會自動為掃描到的未命名 component 分配 name 屬性. 其規則為保持類名不變, 將首字母小寫. 但若類名以兩個或以上連續的大寫字母開始, 則首字母保持不變.
Aliasing a bean outside the bean definition
在一個 bean 的定義內部, 你當然可以通過 name 屬性為這個 bean 提供若干個別名.
但在某些場景下, 這并不能滿足需求. 例如在一個大系統中, 配置文件往往依據子系統被分割成好幾份. 此時如何在另外一個地方為已定義的 bean 取別名呢?
<alias name="fromName" alias="toName"/>
如果你使用 Java 格式的配置文件, @Bean 注解可以用來提供別名信息.
1.3.2. Instantiating beans
若你使用 xml 配置文件, 你通過 <bean/> 元素的 class 屬性來指定待創建對象的類型信息. 這個 class 屬性, 對應于 BeanDefinition 實例中的一個 Class 屬性, 通常是必不可少的 (例外情況請參見: Instantiation using an instance factory method
和 Bean definition inheritance
). 對于 Class 屬性, 你有兩種使用方式:
- 指定待創建對象的類名, 然后容器通過反射調用其構造函數來創建對象.
- 指定一個含有靜態工廠方法的類, 然后容器通過調用其靜態工廠方法來創建對象. 這個靜態工廠方法返回的對象實例的類, 可以和工廠類一致, 也可以毫無關系.
如果你想為一個靜態內部類創建 bean 定義, 其 class 屬性可以定義為
com.example.Foo$Bar
. 注意$
字符的運用.
Instantiation with a constructor
如果你想通過構造器來創建 bean, 只需提供一個 class 屬性即可:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
對于如何提供構造器參數, 如何在一個對象構造完畢后, 去設置它的依賴屬性, 請參閱 Injecting Dependencies.
Instantiation with a static factory method
如果你想通過靜態工廠方法來創建 bean, 你需要指定 class 屬性為包含該靜態工廠方法的類, factory-method 屬性為該靜態工廠方法的方法名. 同時該定義中并不包含待創建 bean 的類型 (class) 信息.
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
對于如何提供靜態工廠方法的方法參數, 如果在一個對象被靜態工廠方法返回后, 去設置它的依賴屬性, 請參閱 Dependencies and configuration in detail
.
Instantiation using an instance factory method
如果你需要通過一個實例工廠方法來創建一個 bean, 那么首先你的容器 (或者父/祖先容器) 里得有這個工廠實例. 然后在 bean 的定義中, class 屬性留空, 指定 factory-bean 屬性為該工廠實例的 name, factory-method 為實例工廠方法的方法名.
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一個工廠類可以同時擁有多個工廠方法.
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
這個例子同時也表明 factory bean 自身也可以作為一個普通的 bean 而被 IoC 容器管理, 通過 DI 來注入所需依賴.
注意 factory bean 和 FactoryBean 的區別. 前者是被 IoC 容器管理的一種 bean, 只是它具有工廠方法而已. 后者是 Spring 框架的一個組件類
FacroryBean
.
1.4. Dependencies
1.4.1. Dependency Injection
依賴注入 (DI) 指的是對象以構造參數, 工廠方法參數或者 setter 方法的形式來定義自己的依賴, 然后容器在創建出這些對象后, 再注入這些對象自己定義的依賴. 由于依賴的注入不是由對象自己來控制的, 所以這一過程又稱為控制反轉 (IoC).
DI 使得你的代碼簡潔且低耦合. 如果對象中的依賴都是基于接口或者抽象基類, 那么單元測試也會變得非常容易.
DI 主要有兩種形式: 基于構造器, 基于 setter 方法.
Constructor-based dependency injection
基于構造器的 DI 實現方式: IoC 容器調用對象的構造函數, 并提供構造函數所需的參數. 基于靜態工廠方法的 DI 與此類似, 不再贅述. 下面是一個基于構造器 DI 的例子, 注意這個類只是一個普通的 POJO:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
Constructor argument resolution
構造器參數的匹配是基于類型的, 如果沒有潛在的歧義, 那么 IoC 容器會按照參數的定義次序依序提供所需的構造參數. 請考慮以下實例:
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
不存在歧義 (假設 Bar 和 Baz 沒有繼承關系), 因此下面的配置文件能夠正常工作, 你不需要顯式地提供構造參數的 index 或者類型信息.
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
在上面的例子中, 因為一旦引用一個 bean, 那么其類型信息就知道了, 所以 IoC 容器可以根據參數的類型信息來進行匹配. 但是對于簡單值, 例如 <value>true</value>
, Spring 不能推斷其類型信息, 因此你需要提供一些額外的信息來幫助匹配:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Constructor argument type matching
針對于以上場景, 你可以通過 type 屬性來顯示提供類型信息:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
Constructor argument index
你也可以通過 index 屬性來顯示提供參數順序信息:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
index 屬性除了能夠解決簡單值的問題, 同樣也能解決多個參數是同一個類型的問題. 注意 index 是從 0 開始的.
Constructor argument name
你同樣可以提供參數名稱:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
請記住要想這種模式正常工作, 你的代碼在編譯時必須打開 debug flag. 如果不能或者不想這么做, 你可以通過 JDK 注解 @ConstructorProperties 來顯式地定名參數名稱:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
Setter-based dependency injection
基于 setter 方法的 DI 實現方式: IoC 容器調用對象的無參構造函數 (或無參靜態工程方法) 來創建對象, 然后通過 setter 方法注入對象的依賴.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
當然, 基于構造器的 DI 和基于 setter 的 DI 可以混合使用.
在 Spring 框架中, 這些依賴是以 BeanDefinition 的形式來定義的, 配合 PropertyEditor 實例, 更可以將依賴屬性從一種類型轉換為另一種類型. 然而, 大部分使用者是不會直接使用這些類的. 你們只需要定義 xml 文件中的 <bean/> 元素, 定義注解類 (被 @Component 注解的類), 或者在 @Configuration 類中定義 @Bean 方法, 然后這些元數據會自動被轉換成 BeanDefinition 實例.
怎樣選擇基于構造器的 DI 還是基于 setter 的 DI?
一個經驗法則是用基于構造器的 DI 來注入必須的依賴, 用基于 setter 的 DI 來注入可選的依賴. 當然, 如果你在 setter 方法上使用 @Required 注解, 那么這個依賴就會變成必須的.
Spring 團隊整體上是推薦基于構造器的 DI 的, 因為這種形式的依賴注入更容易讓你構建一個不可變 (immutable) 對象, 并且保證所需的依賴都不空 (null). 而且當客戶端代碼請求一個對象時, 這種方式總是能夠返回一個已經完全初始化的對象. 基于構造器的 DI 的一個副作用就是, 它容易產生構造參數過多的壞味道. 而一旦出現這種壞味道, 它可能意味著你的類做了太多的事情, 你可能需要重構來使其更符合單一職責原則.
基于 setter 的方式應該主要被用于可選依賴的注入上. 這些可選依賴應該有一個合理的默認值, 不然在你的代碼中就得反復地做非空校驗. 這種方式的好處是, 它可以使對象在某一時刻被重新設置, JMX MBeans 遠程管理就是一個很好的應用場景.
Dependency resolution process
Spring 容器按照如下過程解決依賴問題:
- 首先 Spring 用 beans 的配置元數據來創建 ApplicationContext. 你可以以 xml 文件, Java 代碼或注解的形式來提供這些配置信息.
- 對于每個 bean, 它的依賴是以 JavaBean 屬性, 或者構造函數參數 (或者靜態工廠方法參數) 的形式來表達的. 當這些 bean 被真正創建的時候, 容器會提供這些依賴給它.
- 每個屬性或者構造參數都是一個定義好的值, 或者是指向容器內另一個 bean 的引用.
- 如果屬性或者構造參數是一個定義好的值, 那么 Spring 會將其轉換成所需要的類型. 默認情況下, Spring 能夠將字符串形式的值轉換成 Java 基本類型, 例如 int, long, String, boolean 等等.
Spring 在初始化 IoC 容器的時候會檢查 beans 的配置信息是否合法. 但是只有在一個 bean 被實際創建的時候, 它的依賴才會被注入. 一個 singleton-scoped, pre-instantiated 的 bean 會在容器初始化的時候被創建, 而其他種類的 bean 只會在被請求的時候被創建. 創建一個 bean 可能引起一堆的 bean 被創建, 因為這個 bean 依賴的 bean, 以及它的依賴的依賴, 等等, 需要被事先創建. 注意, 如果 bean 的依賴配置有問題, 這個問題只會在 bean 被實際創建的時候才會暴露.
循環依賴
如果你的代碼主要使用構造器注入, 那么你有可能碰到循環依賴的問題.
例如 class A 的構造器需要一個 class B 的實例, 而 class B 的構造器需要一個 class A 的實例. 如果你在 Spring 中把這兩個類配置成相互通過構造器依賴, 那么 Spring 會在運行時偵測到這種循環依賴, 并拋出BeanCurrentlyInCreationException
異常.
盡管不推薦, 這種循環依賴可以通過 setter 注入來解決. 不同于典型 (無循環依賴) 的情況, 循環依賴的場景下會強制用一個未被完全初始化的 bean 注入進另一個 bean 中 (雞生蛋, 蛋生雞問題).
IoC 容器在初始化的時候會檢測一些問題. 例如依賴于一個不存在的 bean, 循環依賴等. 但是容器只會在一個 bean 被創建的時候才去設置它的屬性, 注入它的依賴, 所以說當一個 IoC 容器被正確地初始化后, 它也可能在運行時掛掉, 比如說你在運行時請求了一個 bean, 而這個 bean 在創建的時候拋了個異常. 為了應對這種潛在問題延遲出現的場景, Spring 的默認配置是 pre-instantiate 單例的 bean, 這樣雖然花費了一些啟動時間和內存, 但是可以使潛在問題盡早暴露. 當然, 你可以改變這種設置, 使 IoC 容器懶加載單例 bean.
在沒有循環依賴的場景下, 當若干個被依賴的 bean 被注入進一個依賴于它們的 bean 中時, 這些被依賴的 bean 已經事先被完全配置好了. 換句話說, IoC 容器創建一個 bean 的過程是: 初始化, 注入依賴, 調用生命周期方法 (Configured init method, InitializingBean callback method 等).
Examples of dependency injection
Setter 注入的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
構造器注入的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
靜態工廠方法的例子:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
靜態工廠方法的參數是通過 <constructor-arg/>
元素來定義的, 就像構造器參數. 靜態工廠方法返回的 bean 的類型不必和工廠類保持一致 (盡管此例是一致的). 實例工廠方法的配置和靜態工廠方法基本一致 (除了使用 factory-bean
屬性而不是 class
屬性), 這里不在贅述.
1.4.2. Dependencies and configuration in detail
Straight values (primitives, Strings, and so on)
你可以通過 value
屬性來提供值, Spring 使用 conversion service
來將這些字符串形式的值轉換成實際所需的類型.
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
下例使用 p-namespace
來簡化配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
以上配置文件雖然簡潔, 但是拼寫錯誤會延遲到運行時才被發現. 建議你使用具有自動提示功能的 IDE, 比如 IntelliJ IDEA.
你可以像這樣配置 java.util.Properties
實例:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器使用 PropertyEditor
機制將 <value/>
元素內的文本轉換成 java.util.Properties
實例中的元素. 這種寫法很方便, 也是為數不多的 Spring 團隊推薦使用 <value/>
元素而不是 value
屬性的場景之一.
The idref element
<idref/>
元素的作用是保證將容器內另一個 bean 的 id 以字符串值的形式 (注意, 不是引用) 正確地傳遞給 <contructor-arg/>
元素或 <property/>
元素.
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
上面地寫法與下面地寫法完全等同:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
然第一種寫法更嚴謹一些, 因為 <idref/>
元素允許容器在程序部署的時候就可以檢查其 id 指向的 bean 是否真實存在. 而第二種寫法不支持這種檢查, 拼寫錯誤只有在 bean 被實際創建后才會被發現. 如果 bean 地類型是 prototype, 那么這種錯誤可能在程序部署完很久以后才會觸發異常.
<idref/>
的一種使用場景是, 在 ProxyFactoryBean
的定義中配置 AOP interceptors, 它可以防止你將某一個 interceptor 的 id 拼錯.
References to other beans (collaborators)
最常用的方式是通過 <ref/>
元素的 bean
屬性來指定依賴的 bean. 它可以指向當前容器或其父容器中的任何一個 bean, 不論其配置信息在不在同一個 xml 文件中. bean
屬性的值可以是要依賴的 bean 的 id, 或其 name 中的任何一個別名.
<ref bean="someBean"/>
通過 <ref/>
中的 parent
屬性可以引用一個父容器中的 bean, 目標 bean 必須是在當前容器的父容器中. 這種方式的主要使用場景為, 你想用代理去包裝一個父容器中的 bean, 且這個代理和父容器中的 bean 名稱保持一致.
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
<ref/>
元素的local
屬性已不再支持, 由bean
屬性替代
Inner beans
在 <property/>
或 <constructor-arg/>
內部定義的 <bean/>
, 稱之為內部 bean.
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
內部 bean 不需要 id 或 name 屬性, 即使你提供了, IoC 容器也不會使用. IoC 容器也會忽略 scope 標識, 內部 bean 永遠只在其宿主 bean 被創建的時候而被創建, 并且是匿名的. 除了宿主 bean, 你不可以將一個內部 bean 注入進容器內另外一個 bean 中, 換句話說, 你無法直接引用一個內部 bean.
作為一種特例, 如果宿主 bean 是單例, 而內部 bean 是自定義的 scope (例如 request-scope), 此時內部 bean 可以收到自定義 scope 的 destruction callbacks: 當內部 bean 被創建后會被綁定到其宿主 bean 上, 但是 destruction callbacks 允許它參與到 request scope 的生命周期中去. 當然, 這只是一種特例, 大部分情況下, 內部 bean 共享其宿主 bean 的 scope.
Collections
對于集合類型, 你可以通過 <list/>
, <set/>
, <map/>
和 <props/>
來設值.
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map 的 key 或 value, 或 set 的 value, 可以用以下任一元素來設置:
bean | ref | idref | list | set | map | props | value | null
Collection merging
Spring 支持集的合并操作. 你可以在父 bean 中定義一個集合, 在子 bean 中定義一個集合, 然后子 bean 中的集合會繼承并覆蓋父 bean 中集合的元素. 不熟悉 bean 繼承機制的讀者, 可以先行參閱相關章節.
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意 merge="true"
屬性的運用. 當名為 child 的 bean 最終被創建后, 它的 adminEmails 屬性將包含以下值:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
對于 <list/>
, <map/>
, <set/>
來說, 這種合并的行為類似. 基于 List 的語義, <list/>
的合并操作有點不一樣: 定義中的元素順序會被保留, 父 bean list 中的元素全部排在子 bean list 元素之前, 所以也不存在元素覆蓋問題.
Limitations of collection merging
你不可以合并不同類型的集合 (例如 Map 和 List), 如果你這么做了, Spring 會拋出一個相應的異常. merge
屬性必須在子 bean 的集合元素中指明, 定義在父 bean 集合元素中的 merge
不會生效.
Strongly-typed collection
從 Java 5 開始, Java 加入了對泛型的支持. 也就是說, 現在你可以聲明一個集合類, 指定它只能包含特定類型的元素 (例如 String). 如果你讓 Spring 來注入一個泛型集合, Spring 的 type-conversion 功能會先將元素轉換成所需的類型, 然后再注入進泛型集合.
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
上例中, Spring 通過反射能夠拿到 Map<String, Float>
的值為 Float
類型, 然后會使用 type conversion 功能將 "9.99", "2.75", "3.99" 這些字符串轉換成 Float 類型.
Null and empty string values
空字符串可以這么設置:
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的配置與以下代碼等效:
exampleBean.setEmail("");
null 可以這么設置:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的配置與下面的代碼等效:
exampleBean.setEmail(null);
XML shortcut with the p-namespace
p-namespace 可以用屬性來代替元素來簡化你的配置.
你可以借助不同的命名空間來擴展 Spring 配置文件的格式, 這些命名空間是基于 XML Schema 定義的. 本章討論的 beans
配置格式, 就是定義于一個 XML Schema 文檔中的. 然而, p-namespace 卻沒有 XSD 文件.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
這個例子顯示了 p-namespace 中的一個屬性 email
, 由于 p-namespace 沒有 Schema 文件, 所以你可以設置任意你需要的屬性 (例如 email
).
以下的例子演示了如何引用另一個 bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
注意 p:spouse-ref="jane"
的運用: spouse
表示依賴屬性的名字, -ref
表示這不是一個普通字符串, 而是一個引用.
p-namespace 有時候并不如標準配置格式來的靈活, 例如有一個依賴屬性的名字以
Ref
結尾, 此時就會與 p-namespace 的語法沖突, 而標準配置格式就不會有這個問題. 我們建議你在選擇配置格式的時候要慎重, 并且經過了與小組成員的充分溝通, 盡量避免在同一份配置文件中同時使用各種配置格式的情況.
XML shortcut with the c-namespace
與 p-namespace 類似, Spring 3.1 引入的 c-namespace 可以簡化構造參數的配置.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
其用法與 p-namespace 類似, 不在贅述.
在極端情況下, 你得不到構造參數的 name (通常是因為你在編譯的時候把 debug flag 關掉了), 此時你仍可以通過參數的 index 來配置:
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于 xml 的語法不允許屬性名以數字開頭, 所以這里的 index 都以
_
作為前綴.
除非你碰到了這種極端情況, 一般我們不建議這種 index 形式的配置.
Compound property names
Spring 支持復合屬性的注入, 只要這個屬性鏈中除了最末位待注入的屬性外, 所有的屬性都不為 null 即可.
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
上例中, 在這個 bean 被創建后, 其 fred 屬性, fred 的 bob 屬性都不能為 null, 否則容器會拋出 NPE 異常.
1.4.3. Using depends-on
如果一個 bean 是另外一個 bean 的依賴, 通常意味著這個 bean 會作為一個屬性被注入進另外一個 bean 中. 一般來說, 在 xml 配置文件中, 你可以通過 <ref/>
元素來達到這個目的. 然而, 有些時候 bean 之間的依賴并不是這樣直接的, 例如某些 bean 在創建之前可能需要另外一些 bean 的靜態初始化被觸發 (像數據庫驅動類的注冊過程). depends-on
屬性可以顯式地要求在這個 bean 被初始化以前, 另外的一些 bean 必須被先初始化完畢.
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
對于多個 bean 的依賴, 可以將這些 bean 的名稱用逗號, 空格或分號隔開:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
如果 bean 的 scope 是單例,
depends-on
不僅控制了初始化順序, 還控制了銷毀順序. 依賴 bean 總是后初始化, 先銷毀; 被依賴 bean 總是先初始化, 后銷毀.
1.4.4. Lazy-initialized beans
默認情況下, ApplicationContext
總是在容器初始化的時候, 就會將所有的單例 bean 都初始化了. 這種行為是合理的, 因為它可以將配置中隱藏的錯誤盡早地暴露出來. 如果你不想要這種行為, 你可以將單例 bean 設置為懶加載模式. 懶加載模式的單例 bean 只會在它被第一次請求時被創建, 而不是隨著 IoC 容器的啟動而被立即初始化.
在 xml 配置文件中, 你可以設置 lazy-init
屬性:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
注意如果一個懶加載模式的單例 bean 被另一個普通的單例 bean 依賴了, 那么這個懶加載的單例 bean 仍然會在 IoC 容器啟動的時候被創建, 因為被依賴的 bean 要在依賴 bean 之前被初始化完畢.
你可以通過 default-lazy-init
來配置容器級別的默認行為:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. Autowiring collaborators
IoC 容器支持自動注入功能. 自動注入功能具有以下優點:
- 自動注入能夠有效地減少依賴屬性或構造參數地配置工作量 (其他的機制像模板配置也能達到這個目的).
- 在開發過程中, 自動配置功能尤其有效. 例如當你的某個 bean 在演進的過程中又增加了一個依賴, 此時你不需要更新配置文件, 自動注入功能會自動注入這個新的依賴. 而當你的代碼趨于穩定后, 你還可以選擇將自動注入功能關閉, 切換回顯式地配置模式.
在 xml 配置文件中, 你可以通過 autowire
屬性來設置自動注入模式. 自動注入有以下四種模式:
Mode | Explanation |
---|---|
no | 默認模式, 不進行自動注入. 對其它 bean 地依賴必須顯式地通過 ref 元素來表達. 在大型應用中不建議更改這一默認行為, 因為顯式的依賴配置能夠使應用更好地被控制, 結構更加地清晰. 某種程度上, 清晰的配置文件可以充當應用結構的文檔來使用. |
byName | 依據屬性的名稱來自動注入. 例如一個 bean 有一個 master 屬性 (具有 setMaster(..) 方法), Spring 會在容器中尋找名稱為 master 的 bean, 然后將尋找到的 bean 注入到依賴 bean 中. |
byType | 依據類型來自動注入. 例如一個 bean 依賴某個屬性, 且在容器中有且僅有一個 bean 的類型同這個屬性一樣, 那么這個 bean 會被注入進依賴 bean 中. 如果發現了多個 bean 的類型與這個屬性一致, Spring 會拋出一個異常. 如果找不到該類型的 bean, 什么事也不會發生, 依賴 bean 的這個屬性不會被設值. |
constructor | 同 byType 一樣, 只不過用構造器注入的方式. 同樣的, 如果某個參數的類型在容器中發現了好幾個 bean, Spring 會拋出異常. |
用 byType 或 constructor 的方式, 你可以自動注入數組類型或泛型集合類型的屬性. 在這種情況下, 容器中所有類型一致的 bean 都會被注入進該屬性中. 如果 Map 的 key 是 String 類型的, 你也可以注入泛型 Map 屬性. 容器中所有類型一致的 bean 都會被當作 value 注入進這個 Map 中, 它們的名稱將作為 key.
你在使用自動注入功能時, 可以配合使用依賴檢查功能, 它在自動注入結束后執行.
Limitations and disadvantages of autowiring
自動注入功能最好是在應用中全局使用. 否則僅僅在幾個 bean 上使用自動注入功能, 會給開發人員造成困擾.
請考慮以下這些限制與缺點:
- 顯式地依賴配置總是會覆蓋自動注入功能. 并且你不能使用自動注入功能來注入簡單屬性 (像 Java 基本類型, Strings, Classes 以及這些類型地數組). 這些限制是特意這么設計的.
- 自動注入功能沒有顯式配置來的精確. 盡管如上表所述, Spring 在有歧義的情況下不會主動猜測結果, 但是 IoC 容器中 beans 的依賴關系卻缺少一份明確的注釋.
- 對于那些借助于 Spring 容器信息的文檔生成工具, 自動注入信息是獲取不到的.
- 如果同一種 Java 類型在容器中有好幾個 bean 定義, 此時對于 byType 自動注入來說, 注入數組, 集合或 Map 都是沒問題的, 但是如果注入單個值的話, 由于沒法判斷那一個 bean 是所需的, Spring 會拋出一個異常.
對于后一種情況, 你有以下選擇:
- 棄用自動注入功能, 轉而使用顯式的e配置.
- 設置
bean
的autowire-candidate
屬性為 false, 來明確地指定該 bean 不作為自動注入功能的候選 bean. - 設置
bean
的primary
屬性為 true, 來明確地指定該 bean 在自動注入場景下作為首選 bean. - 在基于注解地配置中, 對自動注入功能實現更為精細地控制, 參見
Annotation-based container configuration
.
Excluding a bean from autowiring
在 xml 文件中, 你可以將 bean
地 autowire-candidate
屬性設置為 false, 來明確地指定一個 bean 在自動注入場景下不作為候選 bean, 從而使其它的 bean 沒法兒自動注入該 bean. 自動注入場景同時包括基于注解地配置, 例如 @Autowired.
注意,
autowire-candidate
屬性僅僅影響 byType 類型的自動注入功能. 它不影響通過名稱的引用, 因此, byName 的自動注入不受autowired-candidate
屬性影響.
你可以使用基于名稱的模式匹配功能來限制作為自動注入場景下候選 bean 的范圍. 具體的模式可以通過頂層元素 <beans/>
的 default-autowire-candidates
屬性來設置. 例如你想限制候選 bean 的名稱必須以 Repository 結尾, 你可以將模式值設置為 *Repository. 多個模式值可以用逗號隔開. 如果單個 <bean/>
的 autowire-candidate
屬性被明確地設置為 true 或 false 的話, 此時這個配置的優先級高于全局的 default-autowire-candidates
配置.
這個設置只是讓一個 bean 不被其它的 bean 自動注入, 并不代表這個 bean 本身不能使用自動注入功能. 實際上, 這個 bean 可以自由地使用自動注入功能來注入它自己的依賴屬性.
1.4.6. Method injection
在大多數應用場景中, IoC 容器中的大多數 bean 都是單例的. 當一個單例的 bean 依賴于另一個單例的 bean, 或者當一個非單例的 bean 依賴于另一個非單例的 bean 時, 通常你會以將一個 bean 定義為另一個 bean 的屬性的形式來滿足這種依賴. 但是當不同生命周期的 bean 相互依賴時, 就會出現問題. 例如一個 singleton 的 bean A 依賴于一個 prototype 的 bean B, 可能對于 A 中方法的每次調用, 都需要一個全新的 B 實例. 而容器只會創建一次 bean A, 因此也只有一次機會去設置 bean A 的屬性, 容器并不能在 bean A 每次需要 bean B 的時候提供一個全新的 B 實例.
一種解決方式是讓 A 去實現 ApplicationContextAware
接口, 進而持有 ApplicationContext
實例. 這樣在每次需要 (通常是一個全新的) B 實例的時候, 都可以通過 getBean(..)
方法獲得. 只是這種方式有點違背 IoC 原則.
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
以上的這種方式是不被推薦的, 因為這使得你的業務代碼與 Spring API 產生了耦合. Spring IoC 容器提供了一種高級功能: Method Injection, 來滿足這種使用場景.
Lookup method injection
Lookup method injection 使得容器有能力去覆寫一個 bean 的方法, 使這個方法去容器中尋找并返回一個需要的 bean. Spring 框架使用 CGLIB 的動態代理來實現的這個功能.
由于 CGLIB 動態代理是通過繼承來實現的, 所以待繼承的類不能是 final 的, 待覆寫的方法同樣不能是 final 的.
如果是抽象類, 單元測試的時候, 你需要自己去繼承待測試的抽象類并針對抽象方法給出實現.
如果你使用了 Spring 的路徑掃描功能, 此時的類必須是具體類, 而不能是抽象類.
另一個更關鍵的限制是, 工廠方法和 @Bean 方法產生的 bean 不能使用 lookup method 功能, 因為在這兩種情況下, Spring 不負責 bean 實例的創建工作, 也就因此無法在運行時動態地去繼承并創建其子類.
上例中的 CommandManager
類可以重構為一下形式:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
待注入的方法需要滿足以下形式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果這個方法是 abstract
的, 那么動態生成的代理類會去實現這個方法; 否則, 代理類會覆蓋這個方法.
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
注意你需要將 myCommand
定義成 prototype
, 如果定義成 singleton
, 那么 lookup method 每次返回的還是同一個實例.
當然, 你也可以用 @Lookup 注解來定義 lookup method.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者更一般地, 你可以依賴 lookup method 返回值的類型信息:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
注意, 根據 Spring 的組件掃描規則, 抽象類會被忽略. 所以針對上面的抽象類, 如果你使用 Spring 的組件掃描功能, 你還得給出一個具體的實現類.
Arbitrary method replacement
相比于 lookup method 形式的方法注入, 另一種形式的方法注入則可以讓你用任意的實現去替換掉一個 bean 的任意方法.
在 xml 配置文件中, 你可以使用 replace-method
元素去替換一個方法實現. 請考慮一下類中的 computeValue
方法, 我們想覆蓋這個方法:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
一個 org.springframework.beans.factory.support.MethodReplacer
接口的實現類提供了新的方法實現:
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
配置如下:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你可以使用若干個 arg-type
元素來確定一個待覆寫方法的簽名. 如果一個方法被重載了多次, 那么方法簽名的配置是必須的.
1.5. Bean scopes
Spring 原生支持 6 種 scope, 其中 4 種只有在 web 環境中可用. 除此之外, 你也可以創建自定義 scope.
Scope | Description |
---|---|
singleton | 在一個 IoC 容器中, 一個 bean definition 對應一個 object 實例. |
prototype | 一個 bean definition 可以創建任意多個 object 實例. |
request | 在一個 HTTP request 生命周期內, 一個 bean definition 只會創建一個實例. |
session | 在一個 HTTP session 生命周期內, 一個 bean definition 只會創建一個實例. |
application | 在一個 ServletContext 生命周期內, 一個 bean definition 只會創建一個實例. |
websocket | 在一個 WebSocket 生命周期內, 一個 bean definition 只會創建一個實例. |
從 Spring 3.0 開始提供了一個 thread scope, 但是它默認未注冊. 詳細信息請參閱
SimpleThreadScope
的文檔. 關于如何注冊自定義 scope, 請參閱Using a custom scope
章節.
1.5.1. The singleton scope
對于 singleton scope 的 bean, Spring IoC 容器僅僅只創建一個實例并將其緩存起來. 程序中任何地方對于這個 bean 的請求, IoC 容器只會返回這個緩存起來的實例.
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
1.5.2. The prototype scope
對于 prototype 類型的 bean, 每次對它的請求都會導致 IoC 容器創建一個全新的實例.
這里的最佳實踐是, 對于有狀態的 bean, 設置成 prototype, 對于無狀態的 bean, 設置成singleton.
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
注意, 對于 prototype scope 的 bean, Spring 不負責全部的生命周期事件: 只有初始化相關的回調函數會被調用, 所有銷毀相關的回調函數都不會被調用. 客戶端使用完畢后, 得手工清理并釋放資源. 如果想讓 IoC 容器去釋放被 prototype-scoped beans 持有的資源, 請考慮實現一個自定義的 bean post-processor
, 用它來持有這些 bean 的引用.
1.5.3. Singleton beans with prototype-bean dependencies
如果一個 singleton-scoped bean 依賴于一個 prototype-scoped bean, 要注意依賴注入只會在 singleton-scoped bean 初始化的時候發生一次. 就是說, 在 singleton bean 的整個生命周期內, 它持有的 prototype bean 的實例永遠是剛開始被注入的那個. 如果想在運行時不斷地去獲取 prototype bean 的新實例, 依賴注入的方式是不奏效的, 此時請參見 Method injection
章節.
1.5.4. Request, session, application, and WebSocket scopes
這些 scope 只在 web 環境下生效, 例如 XmlWebApplicationContext
.
Initial web configuration
為了支持這些 web-scoped beans, 你需要設置一些初始化過程.
如果你使用 Spring Web MVC, DispatcherServlet
會去處理 request, 你不需要任何初始化設置.
如果你使用其他框架 (例如 JSF 或 Struts), 此時 request 不由 DispatcherServlet 來處理, 你需要注冊一個 ServletRequestListener
: org.springframework.web.context.request.RequestContextListener
. 對于 Servlet 3.0+, 這可以通過 WebApplicationInitializer
接口以編程方式來實現. 對于老版本的 Servlet, 在 web.xml 文件中增加以下配置:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
如果這個 listener 啟動時有異常, 請考慮使用 Spring 的 RequestContextFilter
.
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
, RequestContextListener
和 RequestContextFilter
其實都做了同一件事: 將 HTTP 請求對象綁定到這個請求的服務線程上, 這使得接下來的 request 和 session-scoped beans 得以實現.
Request scope
請考慮以下配置:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
對于每一個 HTTP 請求, IoC 容器都會創建一個新的 loginAction
實例. 你可以隨便更改這個實例的狀態, 因為其它的 HTTP 請求是不會看到這個實例狀態的. 它們只能訪問各自的 loginAction
實例. 當一個請求處理完畢后, 它所對應的 loginAction
實例會被丟棄.
以下是注解配置形式:
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
請考慮以下的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
對于每一個 HTTP session, IoC 容器都會創建一個新的 userPreferences
實例, 你可以隨便更改這個實例的狀態, 因為其它的 HTTP session 是不會看到這個實例狀態的. 它們只能訪問各自的 userPreferences
實例. 當一個 HTTP session 最終被丟棄的時候, 它所對應的 userPreferences
實例會被丟棄.
以下是注解配置形式:
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application scope
請考慮以下配置:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
對于整個 web application, IoC 容器只會創建一個 appPreferences
實例, 并作為一個 ServletContext
的屬性來存儲. 某種程度上來說, 它和 singleton bean 有點像, 但是要注意, session-scoped bean 在整個 ServletContext
中是都是唯一的, 而 singleton-scoped bean 是在 Spring 的 ApplicationContext
中唯一. 一個 web application 可以包含多個 Spring 的 ApplicationContext
容器.
以下是注解配置形式:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
Scoped beans as dependencies