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