天天看點

Spring揭秘 讀書筆記 三 bean的scope與FactoryBean

本書可作為王富強所著<<Spring揭秘>>一書的讀書筆記 

第四章 BeanFactory的xml之旅

bean的scope

scope有時被翻譯為"作用域",scope用來聲明容器中的對象所應該處的限定場景或者說該對象的存活時間,即容器在對象進入其相應的scope之前,生成并裝配這些對象,在該對象不再處于這些scope的限定之後,容器通常會銷毀這些對象。

scope共有5個,singleton,prototype,request,session,global session

前兩個是通用的,中間三個隻是在web系統中才才用到,最後一個global session隻有應用在基于portlet的Web應用程式中才有意義,它映射到portlet的global範圍的session。

我們可以通過使用<bean>的singleton或者scope屬性來指定相應對象的scope,其中,scope屬性隻能在 SD格式的文檔聲明中使用,類似于如下代碼所示範的形式:   

DTD: 

 <bean id="mockObject1" class="...MockBusinessObject" singleton="false"/> 

 XSD: 

 <bean id="mockObject2" class="...MockBusinessObject"  scope="prototype"/>      

singleton

singleton:容器中隻有一個對象,誰要都是這個一個。隻有容器不銷毀或者退出,她就一直存在。

配置情況,因為scope預設的就是singleton,下面三種方式效果一樣。

prototype

<!-- DTD or XSD --> 

 <bean id="mockObject1" class="...MockBusinessObject"/> 

 <!-- DTD --> 

 <bean id="mockObject1" class="...MockBusinessObject" singleton="true"/> 

 <!-- XSD --> 

 < bean id="mockObject1" class="...MockBusinessObject" scope="singleton"/>      

prototype:每次請求都是一個新的對象。而且,一旦這個新的對象給了請求方,那麼容器就不在持有對這個對象的引用。就像嫁出去的姑娘,潑出去的水,娘家以後不管了。

配置方式:

<!-- DTD --> 

 <bean id="mockObject1" class="...MockBusinessObject" singleton="false"/> 

 <!-- XSD --> 

 <bean id="mockObject1" class="...MockBusinessObject"  scope="prototype"/>      

request

request:和prototype沒什麼差別,在web情況下每來一個請求,配置設定一個執行個體。

配置方式

<bean id="requestProcessor" class="...RequestProcessor"  scope="request"/> 

session

session:對于放到session中的資訊,可以将scope設定為session,它除了比request更長的存活時間外,其他方面沒什麼差別。

配置方式:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>  

工廠方法與FactoryBean

我們看下面的代碼:

public class Foo 
{ 
  private BarInterface barInstance; 
  public Foo()    { 
     // 我們應該避免這樣做 
     // instance = new BarInterfaceImpl(); 
  } 
  // ... 
}      

為什麼要避免這麼做?

因為接口與實作類之間有了耦合。

有了耦合咋了,為什麼就不能有耦合。

如果我不想用BarInterfaceImpl,先換成Bar2InterfaceImpl。

上面的方法就是得改Foo類的編碼,如果n個類中都有instance = new BarInterfaceImpl(); 那麼我豈不是得要改n處。

另一方面,如果實作類(例如barInstance)是來自外部jar包的,你沒辦法把它納入spring的管理範圍,哪有如何?

為之奈何?

工廠方法

public class Foo { 

  private BarInterface barInterface;    
  public Foo() { 
    // barInterface = BarInterfaceFactory.getInstance(); 
    // 或者 
    // barInterface = new BarInterfaceFactory().getInstance(); 
  } 
   ... 
}      

這樣一來,Foo直接依賴于BarInterfaceFactory。如果産品有了變化,我隻用改工廠,而不用"告訴"每一個使用者産品改變了。

靜态工廠方式

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

Foo中的BarInterface有對應的get/set方法。

要想通過靜态工廠把BarInterfaceImpl注入到Foo中,我們可以這樣

xml如下:

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

bean中有了factory-method,就說明這個類并不是使用預設的構造方法産生的,而是使用getInstance方法。再換句話說,bar這個bean最後的傳回不是StaticBarInterfaceFactory,而是getInstance這個方法的傳回值。

如果getInstacne需要參數呢?例如:

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

xml如下:

<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"/>      

隻有bar中的constructor-arg下的參數是給getInstance用的,而不是StaticBarInterfaceFactory。(另一方面,靜态工廠也沒有顯示的構造方法呀)

非靜态工廠方式

public class NonStaticBarInterfaceFactory {
  public BarInterface getInstance() { 
     return new BarInterfaceImpl(); 
  }   
  ... 
}      

因為工廠方法為非靜态的,我們隻能通過某個NonStaticBarInterfaceFactory執行個體來調用該方法(哦,錯了,是容器來調用),那麼也就有了如下的配置内容:   

<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"/>      

bar這個bean,中有factory-bean這個屬性,就是告訴容器,bar=barFactory.getInstance();

如果barFactoy的getinstance也需要參數呢?

同樣使用:<constructor-arg>

FactoryBean

上面寫的工廠方法還有一個替代方法,就是FactoryBean

FactoryBean是Spring容器提供的一種可以擴充容器對象執行個體化邏輯的接口,請不要将其與容器名稱BeanFactory相混淆。FactoryBean,其主語是Bean,定語為Factory,也就是說,它本身與其他注冊到容器的對象一樣,隻是一個Bean而已,隻不過,這種類型的Bean本身就是生産對象的工廠(Factory)。 

我們先看看FactoryBean的簽名。                         

public interface FactoryBean { 
  Object getObject() throws Exception; 
  Class getObjectType(); //如果不能确定傳回的類型,getObjectType就傳回null
  boolean isSingleton(); 
}      

完全能看懂,沒有壓力呀。

如果我們想每次得到的日期都是第二天,可以使用如下的代碼

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>      

從xml配置上似乎和以前沒有什麼差别,但是nextDayDateDisplayer裡的屬性dateOfNextDay并不是NextDayDateFactoryBean而是NextDayDateFactoryBean中getObject方法傳回的對象。

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

如果一定要取得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); 
ssertEquals(  ((DateTime)nextDayDate).getDayOfYear(),
      ((DateTime)factoryValue).getDayOfYear()   );