1:
Spring IoC容器的實例化非常簡單,如下面的例子:
Resource resource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
... 或...
ClassPathResource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
... 或...
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
// of course, anApplicationContext
is just aBeanFactory
BeanFactory factory = (BeanFactory) context;
2:
2.1. 用構(gòu)造器來實例化
當采用構(gòu)造器來創(chuàng)建bean實例時,Spring對class并沒有特殊的要求,我們通常使用的class都適用。也就是說,被創(chuàng)建的類并不需要實現(xiàn)任何特定的接口,或以特定的方式編碼,只要指定bean的class屬性即可。不過根據(jù)所采用的IoC類型,class可能需要一個默認的空構(gòu)造器。
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
2.2. 使用 靜態(tài)
工廠方法實例化
當采用靜態(tài)工廠方法創(chuàng)建bean時,除了需要指定class
屬性外,還需要通過factory-method
屬性來指定創(chuàng)建bean實例的工廠方法。Spring將調(diào)用此方法(其可選參數(shù)接下來介紹)返回實例對象,就此而言,跟通過普通構(gòu)造器創(chuàng)建類實例沒什么兩樣。
下面的bean定義展示了如何通過工廠方法來創(chuàng)建bean實例。注意,此定義并未指定返回對象的類型,僅指定該類包含的工廠方法。在此例中, createInstance()
必須是一個static方法。
<bean id="exampleBean"
class="examples.ExampleBean2"
factory-method="createInstance"/>
2.3. 使用實例工廠方法實例化
與使用靜態(tài)工廠方法實例化類似,用來進行實例化的實例工廠方法位于另外一個已有的bean中,容器將調(diào)用該bean的工廠方法來創(chuàng)建一個新的bean實例
為使用此機制,class
屬性必須為空,而factory-bean
屬性必須指定為當前(或其祖先)容器中包含工廠方法的bean的名稱,而該工廠bean的工廠方法本身必須通過factory-method
屬性來設(shè)定(參看以下的例子)。
<!-- the factory bean, which contains a method called createInstance()
-->
<bean id="myFactoryBean" class="...">
...
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="exampleBean"
factory-bean="myFactoryBean"
factory-method="createInstance"/>
3.3.1 . 注入依賴
依賴注入(DI)背后的基本原理是對象之間的依賴關(guān)系(即一起工作的其它對象)只會通過以下幾種方式來實現(xiàn):構(gòu)造器的參數(shù)、工廠方法的參數(shù),或給由構(gòu)造函數(shù)或者工廠方法創(chuàng)建的對象設(shè)置屬性。
DI主要有兩種注入方式,即Setter注入和 構(gòu)造器注入。3.3.1 .1. Setter注入
通過調(diào)用無參構(gòu)造器或無參static
工廠方法實例化bean之后,調(diào)用該bean的setter方法,即可實現(xiàn)基于setter的DI。
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 setMoveFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually 'uses' the injected MovieFinder
is omitted...
}
3.3.1 .2. 構(gòu)造器注入
基于構(gòu)造器的DI通過調(diào)用帶參數(shù)的構(gòu)造器來實現(xiàn),每個參數(shù)代表著一個協(xié)作者。此外,還可通過給靜態(tài)
工廠方法傳參數(shù)來構(gòu)造bean。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the 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...
}最后,我們還要提到的一點就是,當協(xié)作bean被注入到依賴bean時,協(xié)作bean必須在依賴bean之前完全配置好。例如bean A對bean B存在依賴關(guān)系,那么Spring IoC容器在調(diào)用bean A的setter方法之前,bean B必須被完全配置,這里所謂完全配置的意思就是bean將被實例化(如果不是采用提前實例化的singleton模式),相關(guān)的依賴也將被設(shè)置好,而且所有相關(guān)的lifecycle方法(如IntializingBean的init方法以及callback方法)也將被調(diào)用。
3.3.2 . 構(gòu)造器參數(shù)的解析
構(gòu)造器參數(shù)將根據(jù)類型來進行匹配。如果bean定義中的構(gòu)造器參數(shù)類型明確,那么bean定義中的參數(shù)順序就是對應(yīng)構(gòu)造器參數(shù)的順序。考慮以下的類...
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
這里的參數(shù)類型非常明確(當然前提是假定類Bar
與 Baz
在繼承層次上并無任何關(guān)系)。因此下面的配置將會很好地工作,且無須顯式地指定構(gòu)造器參數(shù)索引及其類型。
<beans>
<bean name="foo" class="x.y.Foo">
<constructor-arg>
<bean class="x.y.Bar"/>
</constructor-arg>
<constructor-arg>
<bean class="x.y.Baz"/>
</constructor-arg>
</bean>
</beans>
當引用的bean類型已知,則匹配沒有問題(如上述的例子)。但是當使用象<value>true<value>
這樣的簡單類型時,Spring將無法決定該值的類型,因而僅僅根據(jù)類型是無法進行匹配的。考慮以下將在下面兩節(jié)使用的類:
package examples;
public class ExampleBean {
// No. of years to the 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;
}
}
3.3.2 .1. 構(gòu)造器參數(shù)類型匹配
針對上面的這種情況,我們可以在構(gòu)造器參數(shù)定義中使用type
屬性來顯式的指定參數(shù)所對應(yīng)的簡單類型。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
3.3.2 .2. 構(gòu)造器參數(shù)的索引
通過使用index
屬性可以顯式的指定構(gòu)造器參數(shù)出現(xiàn)順序。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
使用index屬性除了可以解決多個簡單類型構(gòu)造參數(shù)造成的模棱兩可的問題之外,還可以用來解決兩個構(gòu)造參數(shù)類型相同造成的麻煩。注意:index屬性值從0開始。
提示
指定構(gòu)造器參數(shù)索引是使用構(gòu)造器IoC首選的方式。
3.3.3 .1.1. idref
元素
idref
元素用來將容器內(nèi)其它bean的id傳給<constructor-arg/>
或 <property/>
元素,同時提供錯誤驗證功能。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
上述bean定義片段完全地等同于(在運行時)以下的片段:
<bean id="theTargetBean" class="..."/>
<bean id="client" class="...">
<property name="targetName">
<value>theTargetBean</value>
</property>
</bean>
第一種形式比第二種更可取的主要原因是,使用idref
標記允許容器在部署時 驗證所被引用的bean是否存在。而第二種方式中,傳給client
bean的targetName
屬性值并沒有被驗證。任何的輸入錯誤僅在client
bean實際實例化時才會被發(fā)現(xiàn)(可能伴隨著致命的錯誤)。如果client
bean 是prototype類型的bean,則此輸入錯誤(及由此導致的異常)可能在容器部署很久以后才會被發(fā)現(xiàn)。
此外,如果被引用的bean在同一XML文件內(nèi),且bean名字就是bean id,那么可以使用local
屬性,此屬性允許XML解析器在解析XML文件時來對引用的bean進行驗證。
<property name="targetName">
<!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
<idref local="theTargetBean"/>
</property>
3.3.3 .2. 引用其它的bean(協(xié)作者)
在<constructor-arg/>
或<property/>
元素內(nèi)部還可以使用ref
元素。該元素用來將bean中指定屬性的值設(shè)置為對容器中的另外一個bean的引用。如前所述,該引用bean將被作為依賴注入,而且在注入之前會被初始化(如果是singleton bean則已被容器初始化)。
第一種形式也是最常見的形式是通過使用<ref/>
標記指定bean
屬性的目標bean,通過該標簽可以引用同一容器或父容器內(nèi)的任何bean(無論是否在同一XML文件中)。
<ref bean="someBean"/>
第二種形式是使用ref的local
屬性指定目標bean,它可以利用XML解析器來驗證所引用的bean是否存在同一文件中。local
屬性值必須是目標bean的id屬性值。如果在同一配置文件中沒有找到引用的bean,XML解析器將拋出一個例外。如果目標bean是在同一文件內(nèi),使用local方式就是最好的選擇(為了盡早地發(fā)現(xiàn)錯誤)。
<ref local="someBean"/>
第三種方式是通過使用ref的parent
屬性來引用當前容器的父容器中的bean。parent
屬性值既可以是目標bean的id
值,也可以是name
屬性值。而且目標bean必須在當前容器的父容器中。使用parent屬性的主要用途是為了用某個與父容器中的bean同名的代理來包裝父容器中的一個bean
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <-- notice that the name of this bean is the same as the name of 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 as here -->
</bean>
('parent'
屬性的使用并不常見。)
3.3.3 .5. Nulls
<null/>
用于處理null
值。Spring會把屬性的空參數(shù)當作空字符串
處理。以下的xml片斷將email屬性設(shè)為空字符串
。
<bean class="ExampleBean">
<property name="email"><value></value></property>
</bean>
這等同于Java代碼: exampleBean.setEmail("")
。而null
值則可以使用<null>
元素可用來表示。例如:
<bean class="ExampleBean">
<property name="email"><null/></property>
</bean>
上述的配置等同于Java代碼:exampleBean.setEmail(null)
。
3.3.4 . 使用depends-on
多數(shù)情況下,一個bean對另一個bean的依賴最簡單的做法就是將一個bean設(shè)置為另外一個bean的屬性。在xml配置文件中最常見的就是使用<ref/>
元素。有時候它還有另外一種變體,如果一個bean能感知IoC容器,只要給出它所依賴的id,那么就可以通過編程的方式從容器中取得它所依賴的對象。無論采用哪一種方法,被依賴bean將在依賴bean之前被適當?shù)某跏蓟?/p>
在少數(shù)情況下,有時候bean之間的依賴關(guān)系并不是那么的直接(例如,當類中的靜態(tài)塊的初始化被時,如數(shù)據(jù)庫驅(qū)動的注冊)。depends-on
屬性可以用于當前bean初始化之前顯式地強制一個或多個bean被初始化。下面的例子中使用了depends-on
屬性來指定一個bean的依賴。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
若需要表達對多個bean的依賴,可以在'depends-on'
中將指定的多個bean名字用分隔符進行分隔,分隔符可以是逗號、空格及分號等。下面的例子中使用了'depends-on'
來表達對多個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" />
3.3.5 . 延遲初始化bean
ApplicationContext
實現(xiàn)的默認行為就是在啟動時將所有singleton
bean提前進行實例化。提前實例化意味著作為初始化過程的一部分,ApplicationContext
實例會創(chuàng)建并配置所有的singleton bean。通常情況下這是件好事,因為這樣在配置中的任何錯誤就會即刻被發(fā)現(xiàn)(否則的話可能要花幾個小時甚至幾天)。
有時候這種默認處理可能并不是你想要的。如果你不想讓一個singleton bean在ApplicationContext
實現(xiàn)在初始化時被提前實例化,那么可以將bean設(shè)置為延遲實例化。一個延遲初始化bean將告訴IoC 容器是在啟動時還是在第一次被用到時實例化。
在XML配置文件中,延遲初始化將通過<bean/>
元素中的lazy-init
屬性來進行控制。例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
<!-- various properties here... -->
</bean>
<bean name="not.lazy" class="com.foo.AnotherBean">
<!-- various properties here... -->
</bean>
當ApplicationContext
實現(xiàn)加載上述配置時,設(shè)置為lazy
的bean將不會在ApplicationContext
啟動時提前被實例化,而not.lazy
卻會被提前實例化。
模式 |
說明 |
no |
不使用自動裝配。必須通過ref元素指定依賴,這是默認設(shè)置。由于顯式指定協(xié)作者可以使配置更靈活、更清晰,因此對于較大的部署配置,推薦采用該設(shè)置。而且在某種程度上,它也是系統(tǒng)架構(gòu)的一種文檔形式。 |
byName |
根據(jù)屬性名自動裝配。此選項將檢查容器并根據(jù)名字查找與屬性完全一致的bean,并將其與屬性自動裝配。例如,在bean定義中將autowire設(shè)置為by name,而該bean包含master屬性(同時提供setMaster(..)方法),Spring就會查找名為master的bean定義,并用它來裝配給master屬性。 |
byType |
如果容器中存在一個與指定屬性類型相同的bean,那么將與該屬性自動裝配。如果存在多個該類型的bean,那么將會拋出異常,并指出不能使用byType方式進行自動裝配。若沒有找到相匹配的bean,則什么事都不發(fā)生,屬性也不會被設(shè)置。如果你不希望這樣,那么可以通過設(shè)置dependency-check="objects"讓Spring拋出異常。 |
constructor |
與byType的方式類似,不同之處在于它應(yīng)用于構(gòu)造器參數(shù)。如果在容器中沒有找到與構(gòu)造器參數(shù)類型一致的bean,那么將會拋出異常。 |
autodetect |
通過bean類的自省機制(introspection)來決定是使用constructor還是byType方式進行自動裝配。如果發(fā)現(xiàn)默認的構(gòu)造器,那么將使用byType方式。 |
當需要確保bean的所有屬性值(或者屬性類型)被正確設(shè)置的時候,那么這個功能會非常有用。當然,在很多情況下,bean類的某些屬性會具有默認值,或者有些屬性并不會在所有場景下使用,因此這項功能會存在一定的局限性。就像自動裝配一樣,依賴檢查也可以針對每一個bean進行設(shè)置。依賴檢查默認為not,
模式 |
說明 |
none |
沒有依賴檢查,如果bean的屬性沒有值的話可以不用設(shè)置。 |
simple |
對于原始類型及集合(除協(xié)作者外的一切東西)執(zhí)行依賴檢查 |
object |
僅對協(xié)作者執(zhí)行依賴檢查 |
all |
對協(xié)作者,原始類型及集合執(zhí)行依賴檢查 |
3.3.8 . 方法注入
在大部分情況下,容器中的bean都是singleton類型的。如果一個singleton bean要引用另外一個singleton bean,或者一個非singleton bean要引用另外一個非singleton bean時,通常情況下將一個bean定義為另一個bean的property值就可以了。不過對于具有不同生命周期的bean來說這樣做就會有問題了,比如在調(diào)用一個singleton類型bean A的某個方法時,需要引用另一個非singleton(prototype)類型的bean B,對于bean A來說,容器只會創(chuàng)建一次,這樣就沒法在需要的時候每次讓容器為bean A提供一個新的的bean B實例。
3.3.8 .1. Lookup方法注入
這究竟是不是方法注入……
Lookup方法注入利用了容器的覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實例。在上述場景中,Lookup方法注入適用于原型bean(盡管它也適用于singleton bean,但在那種情況下直接注入一個實例就夠了)。Lookup方法注入的內(nèi)部機制是Spring利用了CGLIB庫在運行時生成二進制代碼功能,通過動態(tài)創(chuàng)建Lookup方法bean的子類而達到復寫Lookup方法的目的。
package fiona.apple;
// no more Spring imports!
public class CommandManager {
public Object process(Object command) {
// 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();
}
// mmm, but where is the implementation of this method?
protected abstract CommandHelper createHelper();
}
在包含被注入方法的客戶類中(此處是CommandManager
),此方法的定義必須按以下形式進行:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象
的,動態(tài)生成的子類會實現(xiàn)該方法。否則,動態(tài)生成的子類會覆蓋類里的具體方法。讓我們來看個例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!--commandProcessor
usesstatefulCommandHelper
-->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
在上面的例子中,標識為commandManager的bean在需要一個新的command bean實例時,會調(diào)用createCommand
方法。重要的一點是,必須將command
部署為原型。當然也可以指定為singleton,如果是這樣的話,那么每次將返回相同的command
bean實例!
Lookup方法注入既可以結(jié)合構(gòu)造器注入,也可以與setter注入相結(jié)合。
請注意,為了讓這個動態(tài)子類得以正常工作,需要把CGLIB的jar文件放在classpath里。 另外,Spring容器要子類化的類不能是final
的,要覆蓋的方法也不能是final
的。同樣的,要測試一個包含抽象
方法的類也稍微有些不同,你需要自己編寫它的子類提供該抽象
方法的樁實現(xiàn)。 最后,作為方法注入目標的bean不能是序列化的(serialized)。