深入了解Spring中的容器


1.創建Bean的3種方式

1.1使用構造器創建bean實例

這是最常見的方式,如果不采用構造注入,bean類需要有默認構造函數。如果采用構造注入,則需要配置xml文件的<constructor-arg>

1.2使用靜態工廠方法創建bean

最典型的工廠方法如

 1 package spi;
 2 
 3 public class PersonFactory {
 4     public static Person getPerson(String arg) {
 5         if (arg.equalsIgnoreCase("Chinese")) {
 6             return new Chinese();
 7         } else {
 8             return new American();
 9         }
10     }
11 }

 如果在Spring容器中配置一個bean,bean的實例希望由上的靜態工廠方法反回,則可以在bean中使用 factory-method來指定工廠方法,並用<constructor-arg>指定參數。

1 <bean id="chinese" class="spi.PersonFactory" factory-method="getPerson">
2   <constructor-arg value="chinese" />
3 </bean>

 

1.3實例化工廠方法創建bean

這與上面的使用靜態工廠方法創建bean高度相似,區別是這里是需要先創建工廠的實例。工廠方法如下

 1 package spi;
 2 
 3 public class PersonFactory {
 4     public Person getPerson(String arg) {
 5         if (arg.equalsIgnoreCase("Chinese")) {
 6             return new Chinese();
 7         } else {
 8             return new American();
 9         }
10     }
11 }

 

在xml配置中,除了使用factory-method來指定bean的實例化工廠方法外,還需要使用factory-bean指定工廠實例的bean

1 <bean id="chinese" class="spi.PersonFactory" factory-method="getPerson" factory-bean="personFactory">
2   <constructor-arg value="chinese" />
3 </bean>
4 <bean id="personFactory" class="spi.PersonFactory" />

 

2.bean的繼承

可以在Spring中使用abstract屬性將一個bean定義一個模板配置,這叫做一個抽象bean。抽象bean不能被實例化,只是為了降低配置文件的冗余而存在的,只能由子bean繼承,子類則使用parent屬性指定父類bean。下面是一個例子,

1     <bean id="personTemplate" abstract="true">
2         <property name="name" value="zhangsan" />
3         <property name="axe" value="stoneAxe" />
4     </bean>
5     
6     <bean id="chinesePerson" parent="personTemplate">
7         <property name="axe" value="steelAxe" />
8     </bean>

 

這個例子中,子類bean chinesePerson將會從父類繼承name屬性,但是會覆蓋父類的axe屬性。

注意的是並非父類所有屬性子類都能繼承,depends-on, autowire, singleton, scope, lazy-ini 這些屬性只能從子類本身獲取或采用默認值。

並且Spring容器bean的繼承可以在不同類型的bean之間存在。

3.工廠bean: FactoryBean接口

Spring容器會檢查所有bean,如果發現某個bean實現了FactoryBean接口,就會調用接口的getObject(),其返回值才會作為真正的bean。

開發人員可以重寫getObject()方法供Spring調用,例如下面,

 1 package spi;
 2 
 3 import java.lang.reflect.Field;
 4 
 5 import org.springframework.beans.factory.FactoryBean;
 6 
 7 public class GetField implements FactoryBean<Object>{
 8     private String targetClass;
 9     private String targetField;
10     
11     public String getTargetClass() {
12         return targetClass;
13     }
14 
15     public void setTargetClass(String targetClass) {
16         this.targetClass = targetClass;
17     }
18 
19     public String getTargetField() {
20         return targetField;
21     }
22 
23     public void setTargetField(String targetField) {
24         this.targetField = targetField;
25     }
26 
27     @Override
28     public Object getObject() throws Exception {
29         // TODO Auto-generated method stub
30         Class<?> clazz = Class.forName(targetClass);
31         Field field = clazz.getField(targetField);
32         return field.get(null);
33     }
34 
35     @Override
36     public Class<?> getObjectType() {
37         // TODO Auto-generated method stub
38         return Object.class;
39     }
40 
41     @Override
42     public boolean isSingleton() {
43         // TODO Auto-generated method stub
44         return false;
45     }
46 
47 }

 

這個類實現了FactoryBean接口,可以返回任何我們需要的對象,功能非常強大,在Spring中可以做如下配置,

1     <bean id="getField" class="spi.GetField">
2         <property name="targetClass" value="spi.Chinese" />
3         <property name="targetField" value="axe" />
4     </bean>

 

測試代碼如下,

1     public static void test6() {
2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
3         System.out.println(ctx.getBean("getField"));
4     }

 

程序輸出:North

Spring提供的工廠Bean,大多以FactoryBean結尾,用於生產指定類型的bean,工廠bean是Spring的重要工具之一。

4.獲取Bean本身id

就像Bean希望獲取所在的ApplicationContext實例一樣,有時候Bean還希望獲取XML中配置的id名稱,這種情況需要bean實現BeanNameAware接口,過程與實現ApplicationContextAware類似。並且實現BeanNameAware接口的setBeanName()方法供Spring調用,例如下面,

1 public class Chinese implements Person, BeanNameAware {
2     private String beanName;
3     @Override
4     public void setBeanName(String arg0) {
5         this.beanName = beanName;
6         
7     }
8 ...

 

這樣容器創建bean時候就會調用這個setBeanName()方法,bean就能獲取自己的id名稱了。例如還可以在Chinese類中添加下面的方法,

1     public void info(){
2         System.out.println("我在XML中的id名稱為:"+beanName);
3     }

 

5.強制初始化bean 

某些情況下,容器中的依賴並不是非常直接,初始化bean時候,如果依賴的bean尚未初始化,有可能會出錯,這種情況下我們希望先初始化依賴的bean。

我們可以使用depebds-on屬性來強制容器先初始化依賴額bean,例如

1 <bean id="chinese" class="spi.Chinese" depends-on="steelAxe" />
2 <bean id="steelAxe" class="spi.SteelAxe" />

 

6.容器中Bean的生命周期

對於singleton的bean,Spring可以精確控制生命周期,因此可以在bean的各個階段指定各種行為。

例如在bean依賴注入之后,或者銷毀之前,可以插入指定動作。

6.1依賴關系注入之后的行為

有兩種方式可以在Bean屬性設置成功之后執行特定行為。

  • 一是使用init-method屬性

這種方式要求在bean中定義一個回調方法供Spring調用,並在XML中配置init-method屬性,例如

1 <bean id="chinese" class="spi.Chinese" init-method="init">

 

只要在bean中實現init()方法,就能在bean被設置完屬性之后調用,執行特定方法。

  • 二是讓bean實現InitialializingBean接口,並實現接口的 afterPropertiesSet()方法

這種方式不要求使用init-method屬性,但是這對bean類具有侵入性,不推薦。

6.2Bean銷毀之前的行為

要讓Bean被銷毀之前執行特定行為,也可以類似上面那樣用兩種方式實現,

一是使用destroy-method屬性

二是實現DisposableBean接口,並實現destroy()方法

另外,對於非web應用,如果希望在關閉應用前先關閉容器實例(即ApplicationContext實例),則可以先在JVM中注冊一個鈎子,程序退出時就會執行回調函數,優雅地關閉容器實例,例如,

1     public static void test5() {
2         AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
3         Person p = ctx.getBean("chinese", Person.class);
4         p.useAxe();
5  ctx.registerShutdownHook(); 6     }

 

6.3協調作用域不同步的Bean

如果在一個singleton類型的bean中注入一個prototype類型的bean時候,會發現有個問題,在整個應用程序生命周期,singleton bean只會被初始化一次,然而prototype bean每次都會重新創建並初始化,那么意味着singleton中的依賴注入並不是最新的。

要解決這個問題,Spring使用“方法注入”,具體方式為:

將調用bean(通常是singleton)定義為抽象類,定義一個抽象方法,用來返回一個對象,這個對象將用來對前面的bean注入值

在調用bean中使用 <lookup-method>子元素替代<property>元素,可以使得每次調用bean時候,都會進行一次注入。

bean定義如下

1 class abstract class Chinese implements Person {
2     private Dog dog;
3     public abstract Dog getDog{}
4     ...
5 }

 

XML配置

1 <bean id="chinese" class="spi.Chinese">
2     <lookup-method name="getDog" bean="gunDog" />
3 </bean>
4 <bean id="gunDog" class="spi.GunDog" scope="prototype" />

 

對於上面的配置,Spring會做負責生成Chinese抽象類的子類,並重寫getDog()方法,通常會這樣寫,

1 public Dog getDog(){
2     //獲取ctx
3     ...
4     return ctx.getBean("gunDog");
5 }

這樣就強行要求singleton類型的bean,每次獲取Dog屬性時候,都進行一次依賴注入。

 7.獲取其他bean的屬性值

獲取bean屬性值,即調用getter方法。Spring提供一個工廠Bean : PropertyPathFactoryBean專門用來調用getter方法獲取屬性值。

Spring的工廠Bean很強大,大多以*FactoryBean名稱結尾,每一種工廠Bean可以生產特定產品,PropertyPathFactoryBean則是專門用來獲取屬性值的。

要使用PropertyPathFactoryBean,直接將它配置成一個XML中的bean即可,同時需要配置bean的targetBeanName和propertyPath屬性,下面是一個例子,

 1     <bean id="chinese" class="spi.Chinese" destroy-method="close">
 2         <property name="age" value="30" />
 3         <property name="son">
 4             <bean class="spi.Son">
 5                 <property name="age" value="11" />
 6             </bean>
 7         </property>
 8     </bean>
 9     <!-- 將指定Bean實例的getter方法返回值定義成son1 bean -->
10     <bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
11         <!-- 指定son1 bean來自哪個目標bean的getter方法 -->
12         <property name="targetBeanName" value="chinese" />
13         <!-- 指定目標bean的哪個getter方法, axe代表getSon() -->
14         <property name="propertyPath" value="son" />
15     </bean>

targetBeanName屬性用來指定將要獲取哪個bean的屬性,propertyPath用來指定要執行目標bean的哪個getter方法。

上面涉及一個Son類代碼如下,

 1 package spi;
 2 
 3 public class Son {
 4     private int age;
 5 
 6     public int getAge() {
 7         return age;
 8     }
 9 
10     public void setAge(int age) {
11         this.age = age;
12     }
13     public String toString(){
14         return "Son[age="+this.age+"]";
15     }
16 }

測試代碼如下,

1 System.out.println("系統獲取son1: "+ctx.getBean("son1"));

將會得到如下結果,

系統獲取son1: Son[age=11]

獲取到的bean屬性值還可以用來注入另一個bean,下面是一個例子,

1     <bean id="son2" class="spi.Son">
2         <property name="age">
3         <!-- 使用嵌套bean為setAge()方法指定參數 -->
4         <!-- 以下是訪問指定bean的getter方法的簡單方式, 
5         chinese.son.age代表獲取chinese.getSon().getAge(), 也就是前面的son1-->
6             <bean id="chinese.son.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
7         </property>
8     </bean>

測試代碼,

1 System.out.println("系統獲取son2: "+ctx.getBean("son2"));

執行結果,

1 系統獲取son2: Son[age=11]

另外,propertyPath所指定的getter方法,也可以是一個復合方法(即屬性的屬性),像下面這樣,

1     <bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
2         <property name="targetBeanName" value="chinese" />
3         <!-- 這里的son.age是一個復合屬性,表示chinese.getSon().getAge() -->
4         <property name="propertyPath" value="son.age" />
5     </bean>

 

測試代碼,

1 System.out.println("系統獲取theAge: "+ctx.getBean("theAge"));

 

執行結果,

系統獲取theAge: 11

 

同時,目標bean也可以是嵌入的一個bean,不過這時候不是使用targetBeanName屬性來指定,而是targetObject,例如這樣,

1     <bean id="theAge2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
2         <property name="targetObject">
3             <bean class="spi.American">
4                 <property name="age" value="30" />
5             </bean>
6         </property>
7         <property name="propertyPath" value="age" />
8     </bean>

測試代碼,

1 System.out.println("系統獲取theAge2: "+ctx.getBean("theAge2"));

執行結果,

1 系統獲取theAge2: 30

另外,對於獲取其他bean屬性的工廠Bean配置,還有一種簡化配置如下,

1 <util:property-path id="son1" path="person.son" />

不過這種配置需要導入util:命名空間。

8.獲取Field值

獲取Field分為兩種情況,第一種是靜態Field,第二種是對象實例的Field值。

Spring定義了專門的工廠Bean:FieldRetrievingFactoryBean來獲取Field值。在XML中配置一個FieldRetrievingFactoryBean類型的bean即可。

對於靜態Field情形,需要在XML中設置FieldRetrievingFactoryBean bean的 targetClass(即目標類)和targetFiled(即目標字段)。

對於實例對象的Field,需要指定targetObject(目標對象)和targetField(目標字段)。

對於第二種情況,其實編程中意義不大,因為通常定義在類中的對象實例都是private的,FieldRetrievingFactoryBean無法直接獲取,我們使用PropertyPathFactoryBean調用public的getter方法即可。

對於第一種情況,典型用法如下,

1     <bean id="theAge3" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
2         <property name="targetClass" value="java.sql.Connection" />
3         <property name="targetField" value="TRANSACTION_SERIALIZABLE" />
4     </bean>

 

FieldRetrievingFactoryBean有個setStaticField()方法用來指定目標類和目標Field,因此上面的配置又可以簡寫為,

1     <bean id="theAge4" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
2         <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
3     </bean>

 

FieldRetrievingFactoryBean返回的bean也可以用來注入其他bean,

1     <bean id="son3" class="spi.Son">
2         <property name="age">
3             <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"  class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
4         </property>
5     </bean>

 

上面三段配置的測試代碼如下,

1         System.out.println("系統獲取theAge3: "+ctx.getBean("theAge3"));
2         System.out.println("系統獲取theAge4: "+ctx.getBean("theAge4"));
3         System.out.println("系統獲取son3: "+ctx.getBean("son3"));

 

測試結果,

1 系統獲取theAge3: 8
2 系統獲取theAge4: 8
3 系統獲取son3: Son[age=8]

 

FieldRetrievingFactoryBean也支持命名空間的簡寫,即 

<util:constrant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE" />

9.獲取方法返回值

Spring使用工廠Bean:MethodInvokingFactoryBean來執行bean中的普通方法,如果有返回值,將被賦值給xml中定義的bean

執行普通方法也分兩種情況,一種是靜態類的普通方法,一種是普通對象的方法,

對於兩種情況,都將MethodInvokingFactoryBean定義為XML中一個普通bean,且設置相似的參數。

targetClass/targetObject 用來設置目標類或者目標對象

targetMethod用來設置目標方法

arguments用來設置執行普通方法所需要的參數

一個典型的用法如下,

 1     <!-- 配置 
 2     JFrame win = new JFrame("我的窗口");
 3     win.setVisible(true);
 4      -->
 5      <bean id="win" class="javax.swing.JFrame">
 6          <constructor-arg value="我的窗口" type="java.lang.String" />
 7          <property name="visible" value="true" />
 8      </bean>
 9     <!-- 配置
10     JTextArea jta = JTextArea(7,40);
11      -->
12      <bean id="jta" class="javax.swing.JTextArea">
13          <constructor-arg value="7" type="int" />
14          <constructor-arg value="40" type="int" />
15      </bean>
16      
17      <!-- 配置
18      win.add(new JScrollPane(jta));
19      使用MethodInvokingFactoryBean驅動Spring調用普通方法
20       -->
21       <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
22           <property name="targetObject" ref="win" />
23           <property name="targetMethod" value="add" />
24           <property name="arguments">
25               <list>
26                   <bean class="javax.swing.JScrollPane">
27                       <constructor-arg ref="jta" />
28                   </bean>
29               </list>
30           </property>
31       </bean>

 

上面使用純XML配置的方法,相當於執行了下面的java代碼,

1 JFrame win = new JFrame("我的窗口");
2 win.setVisible(true);
3 JTextArea jta = JTextArea(7,40);
4 win.add(new JScrollPane(jta));

 

通過上面的幾點發現,Spring框架就是通過XML配置來執行java代碼,因此幾乎可以把所有java代碼都放在Spring配置中管理,歸納一下:

  • 調用構造器創建對象,用<bean ..>元素
  • 調用setter方法,用<property..>元素
  • 調用getter方法,用工廠Bean  PropertyPathFactoryBean
  • 調用普通方法,用工廠Bean  MethodInvokingFactoryBean
  • 獲取Field的值,用工廠Bean FieldRetrievingFactoryBean

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM