1. 程式人生 > >Spring揭祕 學習筆記一 (Spring的IoC容器 二)

Spring揭祕 學習筆記一 (Spring的IoC容器 二)

4.3.5 bean的 scope

scope可理解為“作用域”。
scope用來宣告容器中的物件所應該處的限定場景或者說該物件的存活時間,即容器在物件進入其相應的scope之前,生成並裝配這些物件,在該物件不再處於這些scope的限定之後,容器通常會銷燬這些物件。

我們可以通過使用的singleton或者scope屬性來指定相應物件的scope,其中,scope屬性只能在XSD格式的文件宣告中使用,類似於如下程式碼所演示的形式:

DTD:
<bean id="mockObject1" class="...MockBusinessObject" singleton="false"
/> XSD: <bean id="mockObject2" class="...MockBusinessObject" scope="prototype"/>

容器提供的這幾個scope:
①singleton
標記為擁有singleton scope的物件定義,在Spring的IoC容器中只存在一個例項,所有對該物件的引用將共享這個例項。該例項從容器啟動,並因為第一次被請求而初始化之後,將一直存活到容器退出,也就是說,它與IoC容器“幾乎”擁有相同的“壽命”。

圖4-5是Spring參考文件中所給出的singleton的bean的例項化和注入語意演示圖例
這裡寫圖片描述

可以從兩個方面來看待singleton的bean所具有的特性
①物件例項數量。
singleton型別的bean定義,在一個容器中只存在一個共享例項,所有對該型別bean的依賴都引用這一單一例項。
②物件存活時間。
singleton型別bean定義,從容器啟動,到它第一次被請求而例項化開始,只要容器不銷燬或者退出,該型別bean的單一例項就會一直存活。

通常情況下,如果你不指定bean的scope,singleton便是容器預設的scope

② prototype
針對宣告為擁有prototype scope的bean定義,容器在接到該型別物件的請求的時候,會每次都重新生成一個新的物件例項給請求方。雖然這種型別的物件的例項化以及屬性設定等工作都是由容器負責的,但是隻要準備完畢,並且物件例項返回給請求方之後,容器就不再擁有當前返回物件的引用,請求方需要自己負責當前返回物件的後繼生命週期的管理工作,包括該物件的銷燬。

對於那些請求方不能共享使用的物件型別,應該將其bean定義的scope設定為prototype。這樣,每個請求方可以得到自己對應的一個物件例項。

你可以再次瞭解一下擁有prototype scope的bean定義,在例項化物件並注入依賴的時候,它的具體語意是個什麼樣子
這裡寫圖片描述

③ request、session和global session
為它們只適用於Web應用程式,通常是與XmlWebApplicationContext共同使用

request
Spring容器,即XmlWebApplicationContext會為每個HTTP請求建立一個全新的Request-Processor物件供當前請求使用,當請求結束後,該物件例項的生命週期即告結束。從不是很嚴格的意義上說,request可以看作prototype的一種特例

session
Spring容器會為每個獨立的session建立屬於它們自己的全新的UserPreferences物件例項。與request相比,除了擁有session scope的bean的例項具有比request scope的bean可能更長的存活時間,其他方面真是沒什麼差別。

global session
global session只有應用在基於portlet的Web應用程式中才有意義,它對映到portlet的global範圍的session。如果在普通的基於servlet的Web應用中使用了這個型別的scope,容器會將其作為普通session型別的scope對待。

4.3.6 工廠方法與FactoryBean
有時,我們需要依賴第三方庫,需要例項化並使用第三方庫中的相關類,這時,介面與實現類的耦合性需要其他方式來避免

通常的做法是通過使用工廠方法(Factory Method)模式,提供一個工廠類來例項化具體的介面實現類,這樣,主體物件只需要依賴工廠類,具體使用的實現類有變更的話,只是變更工廠類,而主體物件不需要做任何變動

// 程式碼清單4-31 使用了工廠方法模式的Foo類可能定義
public class Foo
{
  private BarInterface barInterface; 
  public Foo()
  {
     barInterface = BarInterfaceFactory.getInstance(); 
   // 或者 
   // barInterface = new BarInterfaceFactory().getInstance();
   // 我們應該避免這樣做
   // instance = new BarInterfaceImpl();
  }
 ...
}

針對使用工廠方法模式例項化物件的方式,Spring的IoC容器同樣提供了對應的整合支援。我們所要做的,只是將工廠類所返回的具體的介面實現類注入給主體物件(這裡是Foo)。

1. 靜態工廠方法(Static Factory Method)
假設某個第三方庫釋出了BarInterface,為了向使用該介面的客戶端物件遮蔽以後可能對BarInterface實現類的變動,同時還提供了一個靜態的工廠方法實現類StaticBarInterfaceFactory,程式碼如下:

public class StaticBarInterfaceFactory
{
  public static BarInterface getInstance()
  {
    return new BarInterfaceImpl();
  }
}

為了將該靜態工廠方法類返回的實現注入Foo,我們使用以下方式進行配置(通過setter方法注入方式為Foo注入BarInterface的例項):

<bean id="foo" class="...Foo">
  <property name="barInterface">
    <ref bean="bar"/>
  </property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

class指定靜態方法工廠類,factory-method指定工廠方法名稱,然後,容器呼叫該靜態方法工廠類的指定工廠方法(getInstance),並返回方法呼叫後的結果,即BarInterfaceImpl的例項。也就是說,為foo注入的bar實際上是BarInterfaceImpl的例項,即方法呼叫後的結果,而不是靜態工廠方法類(StaticBarInterfaceFactory)。

某些時候,有的工廠類的工廠方法可能需要引數來返回相應例項,而不一定非要像我們的getInstance()這樣沒有任何引數。對於這種情況,可以通過<constructor-arg>來指定工廠方法需要的引數

public class StaticBarInterfaceFactory
{
 public static BarInterface getInstance(Foobar foobar)
 {
  return new BarInterfaceImpl(foobar);
 }
}
<bean id="foo" class="...Foo">
  <property name="barInterface">
    <ref bean="bar"/>
   </property>
</bean>
<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance">
  <constructor-arg>
    <ref bean="foobar"/>
  </constructor-arg>
</bean>
<bean id="foobar" class="...FooBar"/>

唯一需要注意的就是,針對靜態工廠方法實現類的bean定義,使用傳入的是工廠方法的引數,而不是靜態工廠方法實現類的構造方法的引數。(況且,靜態工廠方法實現類也沒有提供顯式的構造方法。)

2. 非靜態工廠方法(Instance Factory Method)
因為工廠方法為非靜態的,我們只能通過某個容器來呼叫該方法

<bean id="foo" class="...Foo"> 
  <property name="barInterface">
   <ref bean="bar"/>
  </property> 
</bean> 
<bean id="barFactory" class="...NonStaticBarInterfaceFactory"/>
<bean id="bar" factory-bean="barFactory" factory-method="getInstance"/>

NonStaticBarInterfaceFactory是作為正常的bean註冊到容器的,使用factory-bean屬性來指定工廠方法所在的工廠類例項,而不是通過class屬性來指定工廠方法所在類的型別。指定工廠方法名則相同,都是通過factory-method屬性進行的。

3. FactoryBean
FactoryBean是Spring容器提供的一種可以擴充套件容器物件例項化邏輯的介面。它本身與其他註冊到容器的物件一樣,只是一個Bean而已,只不過,這種型別的Bean本身就是生產物件的工廠。

當某些物件的例項化過程過於煩瑣,通過XML配置過於複雜,使我們寧願使用Java程式碼來完成這個例項化過程的時候,或者,某些第三方庫不能直接註冊到Spring容器的時候,就可以實org.springframework.beans.factory.FactoryBean介面,給出自己的物件例項化邏輯程式碼。

FactoryBean只定義了三個方法

public interface FactoryBean {
  Object getObject() throws Exception;
  Class getObjectType();
  boolean isSingleton();
}

getObject()方法會返回該FactoryBean“生產”的物件例項,我們需要實現該方法以給出自己的物件例項化邏輯;
getObjectType()方法僅返回getObject()方法所返回的物件的型別,如果預先無法確定,則返回null;isSingleton()方法返回結果用於表明,工廠方法(getObject())所“生產”的物件是否要以singleton形式存在於容器中。如果以singleton形式存在,則返回true,否則返回false;

如果我們想每次得到的日期都是第二天

import org.joda.time.DateTime;
import org.springframework.beans.factory.FactoryBean;
public class NextDayDateFactoryBean implements FactoryBean {
 public Object getObject() throws Exception {
  return new DateTime().plusDays(1);
 }
 public Class getObjectType() {
  return DateTime.class;
 }
 public boolean isSingleton() {
  return false;
 }
}

要使用NextDayDateFactoryBean,只需要如下這樣將其註冊到容器即可:

<bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer">
 <property name="dateOfNextDay">
  <ref bean="nextDayDate"/>
 </property>
</bean>
<bean id="nextDayDate" class="...NextDayDateFactoryBean">
</bean>

NextDayDateDisplayer的定義如下:

public class NextDayDateDisplayer
{
  private DateTime dateOfNextDay;
  // 相應的setter方法
  // ...
}

NextDayDateDisplayer所宣告的依賴dateOfNextDay的型別為DateTime,而不是NextDayDateFactoryBean。也就是說FactoryBean型別的bean定義,通過正常的id引用,容器返回的是FactoryBean所“生產”的物件型別,而非FactoryBean實現本身。
如果一定要取得FactoryBean本身的話,可以通過在bean定義的id之前加字首&來達到目的。

Object nextDayDate = container.getBean("nextDayDate"); 
assertTrue(nextDayDate instanceof DateTime); 

Object factoryBean = container.getBean("&nextDayDate");
assertTrue(factoryBean instanceof FactoryBean);
assertTrue(factoryBean instanceof NextDayDateFactoryBean);

Object factoryValue = ((FactoryBean)factoryBean).getObject();
assertTrue(factoryValue instanceof DateTime); 

assertNotSame(nextDayDate, factoryValue);
assertEquals(((DateTime)nextDayDate).getDayOfYear(),((DateTime)factoryValue).getDayOfYear());

4.3.7

我們直接將FX News系統中的FXNewsBean定義註冊到容器中,並將其scope設定為prototype。因為它是有狀態的型別,每條新聞都應該是新的獨立個體;同時,我們給出MockNewsPersister類,使其實現IFXNewsPersister介面,以模擬注入FXNewsBean例項後的情況

public class MockNewsPersister implements IFXNewsPersister {
 private FXNewsBean newsBean;
 public void persistNews(FXNewsBean bean) { 
   persistNewes(); 
 }
 public void persistNews()
 {
   System.out.println("persist bean:"+getNewsBean());
 }
 public FXNewsBean getNewsBean() {
 return newsBean; 
 }
 public void setNewsBean(FXNewsBean newsBean) {
  this.newsBean = newsBean;
 }
}

配置為

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
 <property name="newsBean">
   <ref bean="newsBean"/>
 </property>
</bean>

當多次呼叫MockNewsPersister的persistNews時,會發現結果都是一樣的

BeanFactory container = new XmlBeanFactory(new ClassPathResource(".."));
MockNewsPersister persister = (MockNewsPersister)container.getBean("mockPersister");
persister.persistNews();
persister.persistNews();

這是因為第一個例項注入後,MockNewsPersister再也沒有重新向容器申請新的例項。所以,容器也不會重新為其注入新的FXNewsBean型別的例項。

解決問題的關鍵在於保證getNewsBean()方法每次從容器中取得新的FXNewsBean例項,而不是每次都返回其持有的單一例項。

1. 方法注入(Method Injection)
只要讓getNewsBean方法宣告符合規定的格式,並在配置檔案中通知容器,當該方法被呼叫的時候,每次返回指定型別的物件例項即可。

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

也就是說,該方法必須能夠被子類實現或者覆寫,因為容器會為我們要進行方法注入的物件使用Cglib動態生成一個子類實現,從而替代當前物件。

配置內容

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
  <lookup-method name="getNewsBean" bean="newsBean"/>
</bean>

通過<lookup-method>的name屬性指定需要注入的方法名,bean屬性指定需要注入的物件,當getNewsBean方法被呼叫的時候,容器可以每次返回一個新的FXNewsBean型別的例項。

使用BeanFactoryAware介面
即使沒有方法注入,只要在實現getNewsBean()方法的時候,能夠保證每次呼叫BeanFactory的getBean(“newsBean”),就同樣可以每次都取得新的FXNewsBean物件例項。現在,我們唯一需要的,就是讓MockNewsPersister擁有一個BeanFactory的引用。

Spring框架提供了一個BeanFactoryAware介面,容器在例項化實現了該介面的bean定義的過程中,會自動將容器本身注入該bean。這樣,該bean就持有了它所處的BeanFactory的引用

public interface BeanFactoryAware {
  void setBeanFactory(BeanFactory beanFactory) throws BeansException; 
}

讓MockNewsPersister實現該介面以持有其所處的BeanFactory的引用

public class MockNewsPersister implements IFXNewsPersister,BeanFactoryAware {
  private BeanFactory beanFactory;
  public void setBeanFactory(BeanFactory bf) throws BeansException 
  {
   this.beanFactory = bf;
  }
  public void persistNews(FXNewsBean bean) { 
    persistNews();
  }
  public void persistNews() {
    System.out.println("persist bean:"+getNewsBean());
  }
  public FXNewsBean getNewsBean() { 
    return beanFactory.getBean("newsBean");
  }
}

配置為

<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
</bean>

使用ObjectFactoryCreatingFactoryBean
ObjectFactoryCreatingFactoryBean是Spring 提供的一個FactoryBean實現,它返回一個ObjectFactory例項。從ObjectFactoryCreatingFactoryBean返回的這個ObjectFactory例項可以為我們返回容器管理的相關物件。實際上, ObjectFactoryCreatingFactoryBean 實現了BeanFactoryAware介面,它返回的ObjectFactory例項只是特定於與Spring容器進行互動的一個實現而已。使用它的好處就是,隔離了客戶端物件對BeanFactory的直接引用。

public class MockNewsPersister implements IFXNewsPersister {
  private ObjectFactory newsBeanFactory;
  public void persistNews(FXNewsBean bean) {
    persistNews();
  }
  public void persistNews()
  {
    System.out.println("persist bean:"+getNewsBean());
  }
  public FXNewsBean getNewsBean() {
    return newsBeanFactory.getObject();
  }
  public void setNewsBeanFactory(ObjectFactory newsBeanFactory) {
    this.newsBeanFactory = newsBeanFactory;
  }
}
<bean id="newsBean" class="..domain.FXNewsBean" singleton="false">
</bean>
<bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
  <property name="targetBeanName">
    <idref bean="newsBean"/>
  </property>
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
  <property name="newsBeanFactory">
    <ref bean="newsBeanFactory"/>
  </property>
</bean>

3. 方法替換
方法替換更多體現在方法的實現層面上,它可以靈活替換或者說以新的方法實現覆蓋掉原來某個方法的實現邏輯。

想替換掉FXNewsProvider的getAndPersistNews方法預設邏輯,這時,我就可以用方法替換將它的原有邏輯給替換掉
首先,我們需要給出MethodReplacer的實現類,在這個類中實現將要替換的方法邏輯。

public class FXNewsProviderMethodReplacer implements MethodReplacer { 

 private static final transient Log logger = 
   LogFactory.getLog(FXNewsProviderMethodReplacer.class);

 public Object reimplement(Object target, Method method, Object[] args) 
 throws Throwable {
  logger.info("before executing method["+method.getName()+"] on  Object["+target.getClass().getName()+"].");
   System.out.println("sorry,We will do nothing this time.");
  logger.info("end of executing method["+method.getName()+"] on Object["+target.getClass().getName()+"].");
  return null;
 } 
}

把這個邏輯通過<replaced-method>配置到FXNewsProvider的bean定義中,使其生效

<bean id="djNewsProvider" class="..FXNewsProvider">
 <constructor-arg index="0">
   <ref bean="djNewsListener"/>
 </constructor-arg>
 <constructor-arg index="1">
   <ref bean="djNewsPersister"/>
 </constructor-arg>
 <replaced-method name="getAndPersistNews" replacer="providerReplacer">
 </replaced-method>
</bean>
<bean id="providerReplacer" class="..FXNewsProviderMethodReplacer">
</bean>

4.4 容器背後的祕密

這裡寫圖片描述

4.4.1 “戰略性觀望”

Spring的IoC容器所起的作用,就像圖4-7所展示的那樣,它會以某種方式載入Configuration Metadata(通常也就是XML格式的配置資訊),然後根據這些資訊繫結整個系統的物件,最終組裝成一個可用的基於輕量級容器的應用系統。

Spring的IoC容器實現以上功能的過程,基本上可以按照類似的流程劃分為兩個階段,即容器啟動階段和Bean例項化階段
這裡寫圖片描述

1. 容器啟動階段
首先會通過某種途徑載入Configuration MetaData。除了程式碼方式比較直接,在大部分情況下,容器需要依賴某些工具類(BeanDefinitionReader)對載入的Configuration MetaData進行解析和分析,並將分析後的資訊編組為相應的BeanDefinition,最後把這些儲存了bean定義必要資訊的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器啟動工作就完成了。
這裡寫圖片描述
總地來說,該階段所做的工作可以認為是準備性的,重點更加側重於物件管理資訊的收集。

2. Bean例項化階段
經過第一階段,現在所有的bean定義資訊都通過BeanDefinition的方式註冊到BeanDefinitionRegistry
中。當某個請求方通過容器的getBean方法明確地請求某個物件,或者因依賴關係容器需要隱式地呼叫getBean方法時,就會觸發第二階段的活動。

該階段,容器會首先檢查所請求的物件之前是否已經初始化。如果沒有,則會根據註冊的BeanDefinition所提供的資訊例項化被請求物件,併為其注入依賴。如果該物件實現了某些回撥介面,也會根據回撥介面的要求來裝配它。當該物件裝配完畢之後,容器會立即將其返回請求方使用。如果說第一階段只是根據圖紙裝配生產線的話,那麼第二階段就是使用裝配好的生產線來生產具體的產品了。

4.4.2 插手“容器的啟動”

Spring提供了一種叫做BeanFactoryPostProcessor的容器擴充套件機制。該機制允許我們在容器例項化相應物件之前,對註冊到容器的BeanDefinition所儲存的資訊做相應的修改。這就相當於在容器實現的第一階段最後加入一道工序,讓我們對最終的BeanDefinition做一些額外的操作,比如修改其中bean定義的某些屬性,為bean定義增加其他資訊等。

PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是兩個比較常用的BeanFactoryPostProcessor。

我 們 可 以 通 過兩種方式來應用BeanFactoryPostProcessor , 分別針對基本的IoC 容器BeanFactory和較為先進的容器ApplicationContext。

對於BeanFactory來說,我們需要用手動方式應用所有的BeanFactoryPostProcessor

// 宣告將被後處理的BeanFactory例項
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
// 宣告要使用的BeanFactoryPostProcessor
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("..."));
// 執行後處理操作 
propertyPostProcessor.postProcessBeanFactory(beanFactory);

對於ApplicationContext來說因為ApplicationContext會自動識別配置檔案中的BeanFactoryPostProcessor並應用它,所以,相對於BeanFactory,在ApplicationContext中載入並應用BeanFactoryPostProcessor,僅需要在XML配置檔案中將這些BeanFactoryPostProcessor簡單配置一下即可

... 
<beans>
 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
   <list>
    <value>conf/jdbc.properties</value>
    <value>conf/mail.properties</value> 
   </list>
  </property>
 </bean>
... 
</beans>

下面讓我們看一下Spring提供的這幾個BeanFactoryPostProcessor實現都可以完成什麼功能
1. PropertyPlaceholderConfigurer
PropertyPlaceholderConfigurer允許我們在XML配置檔案中使用佔位符(PlaceHolder),並將這些佔位符所代表的資源單獨配置到簡單的properties檔案中來載入

以資料來源的配置為例,使用了PropertyPlaceholderConfigurer之後可以在XML配置檔案中按照程式碼清單4-43所示的方式配置資料來源,而不用將連線地址、使用者名稱和密碼等都配置到XML中。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" estroy-method="close">
 <property name="url">
  <value>${jdbc.url}</value>
 </property>
 <property name="driverClassName">
  <value>${jdbc.driver}</value>
 </property>
 <property name="username">
  <value>${jdbc.username}</value>
 </property>
 <property name="password">
  <value>${jdbc.password}</value>
 </property>
 ...
 <property name="maxActive">
   <value>100</value>
 </property>
</bean>

現在,所有這些佔位符所代表的資源,都放到了jdbc.properties檔案中,如下所示:

jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=your username
jdbc.password=your password

當BeanFactory在第一階段載入完成所有配置資訊時,BeanFactory中儲存的物件的屬性資訊還只是以佔位符的形式存在,如jdbc.url{jdbc.driver}。當PropertyPlaceholderConfigurer作為BeanFactoryPostProcessor被應用時,它會使用properties配置檔案中的配置資訊來替換相應BeanDefinition中佔位符所表示的屬性值。這樣,當進入容器實現的第二階段例項化bean時,bean定義中的屬性值就是最終替換完成的了。

PropertyPlaceholderConfigurer不單會從其配置的properties檔案中載入配置項,同時還會檢查Java的System類中的Properties,可以通過setSystemPropertiesMode()或者setSystemPropertiesModeName()來控制是否載入或者覆蓋System相應Properties的行為。

2. PropertyOverrideConfigurer
可以通過PropertyOverrideConfigurer對容器中配置的任何你想處理的bean定義的property資訊進行覆蓋替換。

如果要對容器中的某些bean定義的property資訊進行覆蓋,我們需要按照如下規則提供一個PropertyOverrideConfigurer使用的配置檔案:
beanName.propertyName=value

下面是針對dataSource定義給出的PropertyOverrideConfigurer的propeties檔案配置資訊:

# pool-adjustment.properties 
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50

這樣,當按照如下程式碼,將PropertyOverrideConfigurer載入到容器之後,dataSource原來定義的預設值就會被pool-adjustment.properties檔案中的資訊所覆蓋:

<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
  <property name="location" value="pool-adjustment.properties"/>
</bean>

pool-adjustment.properties中沒有提供的配置項將繼續使用原來XML配置中的預設值。
當容器中配置的多個PropertyOverrideConfigurer對同一個bean定義的同一個property值進行處理的時候,最後一個將會生效

3. CustomEditorConfigurer
CustomEditorConfigurer是另一種型別的BeanFactoryPostProcessor實現,它只是輔助性地將後期會用到的資訊註冊到容器,對BeanDefinition沒有做任何變動。

XML所記載的,都是String型別,即容器從XML格式的檔案中讀取的都是字串形式,最終應用程式卻是由各種型別的物件所構成。要想完成這種由字串到具體物件的轉換,都需要這種轉換規則相關的資訊,而CustomEditorConfigurer就是幫助我們傳達類似資訊的。

Spring容器內部在做具體的型別轉換的時候,會採用JavaBean框架內預設的PropertyEditor搜尋邏輯,從而繼承了對原生型別以及java.lang.String.java.awt.Color和java.awt.Font等型別的轉換支援。同時,Spring框架還提供了自身實現的一些PropertyEditor,這些PropertyEditor大部分都位於org.springframework. beans.propertyeditors包下。

自定義PropertyEditor
假設需要對yyyy/MM/dd形式的日期格式轉換提供支援。通常情況下,我們可以直接繼java.beans.PropertyEditorSupport類以避免實現java.beans.PropertyEditor介面的所有方法

public class DatePropertyEditor extends PropertyEditors{
 private String datePattern;
 @Override
 public void setAsText(String text) throws IllegalArgumentException {
   DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern());
   Date dateValue = dateTimeFormatter.parseDateTime(text).toDate();
   setValue(dateValue);
 }
 public String getDatePattern(){
   return datePattern;
 }
 public void setDatePattern(String datePattern){
   this.dataPattern=datePattern;
 }
}

通過CustomEditorConfigurer註冊自定義的PropertyEditor
預設情況下,Spring容器找不到合適的PropertyEditor將字串“2007/10/16”轉換成物件所宣告的java.util.Date型別。所以,我們通過CustomEditorConfigurer將剛實現的DatePropertyEditor註冊到容器,以告知容器按照DatePropertyEditor的形式進行String到Date型別的轉換工作

如果使用的容器是BeanFactory的實現,比如XmlBeanFactory,就需要通過編碼手動應用CustomEditorConfigurer到容器,類似如下形式:

XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
//
CustomEditorConfigurer ceConfigurer = new CustomEditorConfigurer();
Map customerEditors = new HashMap();
customerEditors.put(java.util.Date.class, new DatePropertyEditor());
ceConfigurer.setCustomEditors(customerEditors);
//
ceConfigurer.postProcessBeanFactory(beanFactory);

但如果使用的是ApplicationContext相應實現,因為ApplicationContext會自動識別BeanFactoryPostProcessor並應用,所以只需要在相應配置檔案中配置一下

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
 <property name="customEditors">
  <map>
   <entry key="java.util.Date">
     <ref bean="datePropertyEditor"/>
   </entry>
  </map>
 </property>
</bean>
<bean id="datePropertyEditor" class="...DatePropertyEditor">
  <property name="datePattern">
    <value>yyyy/MM/dd</value>
  </property>
</bean>

提倡使用propertyEditorRegistrars屬性來指定自定義的PropertyEditor。不過,這樣我們就需要再多做一步工作,就是給出一個org.springframePropertyEditorRegistrar的實現。

public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
 private PropertyEditor propertyEditor;
 public void registerCustomEditors(PropertyEditorRegistry peRegistry) {
   peRegistry.registerCustomEditor(java.util.Date.class, getPropertyEditor());
 }
 public PropertyEditor getPropertyEditor() {
   return propertyEditor;
 }
 public void setPropertyEditor(PropertyEditor propertyEditor) {
   this.propertyEditor = propertyEditor;
 }
} 

配置方式

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
 <property name="propertyEditorRegistrars">
  <list>
    <ref bean="datePropertyEditorRegistrar"/>
  </list>
 </property>
</bean>
<bean id="datePropertyEditorRegistrar" class="...DatePropertyEditorRegistrar">
 <property name="propertyEditor">
   <ref bean="datePropertyEditor"/>
 </property>
</bean>
<bean id="datePropertyEditor" class="...DatePropertyEditor">
  <property name="datePattern">
     <value>yyyy/MM/dd</value>
  </property>
</bean>

4.4.3 瞭解bean的一生

在已經可以藉助於BeanFactoryPostProcessor來干預Magic實現的第一個階段動之後,我們就可以開始探索下一個階段,即bean例項化階段的實現邏輯了。

只有當請求方通過BeanFactory的getBean()方法來請求某個物件例項的時候,才有可能觸發Bean例項化階段的活動。BeanFactory的getBean法可以被客戶端物件顯式呼叫,也可以在容器內部隱式地被呼叫。隱式呼叫有如下兩種情況:
①對於BeanFactory來說,物件例項化預設採用延遲初始化。通常情況下,當物件A被請求而需要第一次例項化的時候,如果它所依賴的物件B之前同樣沒有被例項化,那麼容器會先例項化物件A所依賴的物件。
②ApplicationContext啟動之後會例項化所有的bean定義它會在啟動階段的活動完成之後,緊接著呼叫註冊到該容器的所有bean定義的例項化方法getBean()。

只有當對應某個bean定義的getBean()方法第一次被呼叫時,不管是顯式的還是隱式的,Bean例項化階段的活動才會被觸發,第二次被呼叫則會直接返回容器快取的第一次例項化完的物件例項(prototype型別bean除外)。
這裡寫圖片描述

1. Bean的例項化與BeanWrapper
容器在內部實現的時候,採用“策略模式(Strategy Pattern)”來決定採用何種方式初始化bean例項。通常,可以通過反射或者CGLIB動態位元組碼生成來初始化相應的bean例項或者動態生成其子類。

InstantiationStrategy定義是例項化策略的抽象介面,其直接子類SimpleInstantiationStrategy實現了簡單的物件例項化功能,可以通過反射來例項化物件例項,但不支援方法注入方式的物件例項化。CglibSubclassingInstantiationStrategy繼承了SimpleInstantiationStrategy的以反射方式例項化物件的功能,並且通過CGLIB的動態位元組碼生成功能,該策略實現類可以動態生成某個類的子類,進而滿足了方法注入所需的物件例項化需求。預設情況下,容器內部採用的是CglibSubclassingInstantiationStrategy。

容器只要根據相應bean定義的BeanDefintion取得例項化資訊,結合CglibSubclassingInstantiationStrategy以及不同的bean定義型別,就可以返回例項化完成的物件例項。返回方式上有些“點綴”。不是直接返回構造完成的物件例項,而是以BeanWrapper對構造完成的物件例項進行包裹,返回相應的BeanWrapper例項。

BeanWrapper介面通常在Spring框架內部使用,它有一個實現類BeanWrapperImpl。其作用是對某個bean進行“包裹”,然後對這個“包裹”的bean進行操作,比如設定或者獲取bean的相應屬性值。而在第一步結束後返回BeanWrapper例項而不是原先的物件例項,就是為了第二步“設定物件屬性”

BeanWrapper定義繼承了PropertyAccessor介面,可以以統一的方式對物件屬性進行訪問;BeanWrapper定義同時又直接或者間接繼承了PropertyEditorRegistry和TypeConverter介面。在第一步構造完成物件之後,Spring會根據物件例項構造一個BeanWrapperImpl例項,然後將之前CustomEditorConfigurer註冊的PropertyEditor複製一份給BeanWrapperImpl例項(這就是BeanWrapper同時又是PropertyEditorRegistry的原因)。

程式碼清單4-49 使用BeanWrapper操作物件,可以免去直接使用Java反射API(Java Reflection API)操作物件例項的煩瑣。

Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); 
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();

BeanWrapper newsProvider = new BeanWrapperImpl(provider); 
newsProvider.setPropertyValue("newsListener", listener);
newsProvider.setPropertyValue("newPersistener", persister);

assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrappedInstance());
assertSame(listener, newsProvider.getPropertyValue("newsListener")); 
assertSame(persister, newsProvider.getPropertyValue("newPersistener"));

直接使用Java反射API是如何實現的(忽略了異常處理相關程式碼)

Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); 
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();

Class providerClazz = provider.getClass();
Field listenerField = providerClazz.getField("newsListener");
listenerField.set(provider, listener);
Field persisterField = providerClazz.getField("newsListener");
persisterField.set(provider, persister);

assertSame(listener, listenerField.get(provider)); 
assertSame(persister, persisterField.get(provider));

2. 各色的Aware介面
當物件例項化完成並且相關屬性以及依賴設定完成之後,Spring容器會檢查當前物件例項是否實現了一系列的以Aware命名結尾的介面定義。如果是,則將這些Aware介面定義中規定的依賴注入給當前物件例項。

針對BeanFactory型別的容器,Aware介面為如下幾個:
BeanNameAware
如果Spring容器檢測到當前物件例項實現了該介面,會將該物件例項