別再BeanFactory和FactoryBean傻傻分不清楚

別再BeanFactory和FactoryBean傻傻分不清楚

寫在前面

Spring系列,我打算按照如下類圖的方式來一點點的深入學習,所以我們從BeanFactory和FactoryBean開始下手。
別再BeanFactory和FactoryBean傻傻分不清楚

前言

衆所周知,在Spring中有兩個特別容易搞混的概念,那就是BeanFactory和FactoryBean兩兄弟。上篇文章我們聊了BeanFactory,它的主語是Factory,定語是Bean,也就是說它是管理Bean的工廠。我們需要使用某個Bean時只需要通過它就能獲得。而對於FactoryBean,它的主語是Bean,定語是Factory,所以它本質上是一個Bean,與其他被註冊到Spring容器中的Bean一樣,只不過,該Bean比較特殊,它本身就是生產Bean的工廠(這就好比女人是一個人,但是女人比較特殊,她可以生產人)。

場景引入

工作中,我們經常強調"面向接口編程",但你想:雖然我們可以通過接口來避免和實現類耦合,但總歸要有一種方式可以將接口和實現類進行關聯纔行。如果實現類是我們自己開發就比較簡單:通過依賴注入的方式,讓容器來解除接口和實現類的耦合性。但有時候實現類並不是由我們開發,有一種情況是實現類是在運行時根據某些信息自動生成,生成後還要交給容器管理(比如mybatis的mapper接口的代理類),那這個情況怎麼辦呢?一般我們採用工廠方法模式(Factory-Method)來解決。如下代碼:

public interface AirConditioner {
  // 空調接口
}

public class Car {
  private AirConditioner conditioner;
  public Car() {
    this.conditioner = AirConditionerFactory.getInstance();
    // 或者如下方式
    // this.conditioner = new AirConditionFactory.getInstance();
  }
  // getter setter 方法略
}

public class AirConditionerFactory {
  public static AirConditioner getInstance(){
    // 動態生成AirConditioner的代理類實現
  }
  // 或者如下方式
  //public AirConditioner getInstance(){
    // 動態生成AirConditioner的代理類實現
  //}
}

Spring中的解決方案

針對上面這樣使用工廠方法模式實例化對象的方式,在Spring中IOC容器給我們提供了對應的支持。我們只需要將工廠返回的接口的具體實現類注入給依賴該接口的對象就可以了。具體方式一共有以下三種,我們一一來了解。

通過靜態工廠方法實現

public class AirConditionerFactory {
  public static AirConditioner getInstance(){
    // 動態生成AirConditioner的代理類實現
  }
}

在配置文件中,我們進行如下配置:

<bean id="airConditioner" class="xxx.AirConditionerFactory" factory-method="getInstance"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

對於airConditioner這個bean來說,class是我們自己創建的靜態方法工廠,factory-method是工廠的靜態方法名。在運行時,容器調用該靜態方法工廠類的靜態方法,獲取AirConditioner的實例,而後爲car這個bean注入該實例。而不是注入靜態工廠方法類(AirConditionerFactory)的實例。

靜態工廠方法類的類型和靜態方法返回的類型並沒有必然的聯繫,更不需要一定相同。

另外,我們生成具體實現時,有可能依賴一些參數,那怎麼實現參數傳遞呢?具體方式如下:

public class AirConditionerFactory {
  public static AirConditioner getInstance(String clazz){
    // 動態生成AirConditioner的代理類實現
  }
}

此時,配置方式就變成下面這樣了:

<bean id="airConditioner" class="xxx.AirConditionerFactory" factory-method="getInstance">
  <constructor-arg>xxx.AirConditioner</constructor-arg>
</bean>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

這裏我們需要說明的是:配置文件中通過constructor-arg傳入的是靜態方法的參數,而不是靜態工廠類的構造函數的參數。

通過非靜態工廠方法實現

我們通過靜態工廠方法可以實現將靜態方法的調用結果註冊到容器中,那我們肯定也可以基於非靜態工廠來實現。代碼如下:

public class AirConditionerFactory {
  public AirConditioner getInstance(){
    // 動態生成AirConditioner的代理類實現
  }
}

因爲工廠方法不是靜態的,所以我們只有依賴該工廠的實例才能得到具體的實現類,配置方式如下:

<bean id="airConditionerFactory" class="xxx.AirConditionerFactory" />
<bean id="airConditioner" factory-bean="airConditionerFactory" factory-method="getInstance"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

可以看到,這種方式下AirConditionerFactory是作爲正常的bean被註冊到容器裏的。而airConditioner在定義時,factory-bean屬性用於指定工廠方法的實例,factory-method屬性用於指定工廠方法名。

非靜態工廠方法需要傳遞參數時,用法和靜態工廠類似,都是用constructor-arg傳入。

通過FactoryBean實現

像本文的情景,或者某些對象實例化過程過於複雜,我們就可以通過實現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的方式存在於容器中。

這種方式的具體實現方式如下:

public class AirConditionerFactory implements FactoryBean {
  public Object getObject() {
    // 動態生成AirConditioner的代理類實現
  }
  public Class getObjectType() {
    return AirConditioner.class;
  }
  public boolean isSingleton() {
    return true;
  }
}

至於配置方式,就更簡單了,如下:

<bean id="airConditioner" class="xxx.AirConditionerFactory"/>
<bean id="car" class="xxx.Car">
    <property name="conditioner" ref="airConditioner"/>
</bean>

單純從配置文件來看,跟使用普通的bean沒有任何區別,但是通過定義的airConditioner作爲id去容器裏獲取bean,得到的卻是FactoryBean生產的對象,而不是FactoryBean實例。

那如果我們就是想要獲取該FactoryBean對象實例怎麼辦呢?別急,我們只需要在該bean定義的id前面加上"&"前綴再去容器裏獲取就可以獲取到FactoryBean本身的實例了。(這是不是有點C語言中的取地址的味道,其實Spring官方稱它爲解引用)

寫在最後

其實,在Spring容器的內部,許多地方都是用了FactoryBean,我們在這裏列舉出一些常見的FactoryBean的實現:

  • JndiObjectFactoryBean
  • LocalSessionFactoryBean
  • SqlMapClientFactoryBean
  • ProxyFactoryBean
  • TransactionProxyFactoryBean

它們隱藏了實例化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支持泛型,即接口聲明改爲FactoryBean的形式。而且FactoryBean在AOP和事務中都發揮着不替代的作用。這個我們後面會慢慢接觸到~
別再BeanFactory和FactoryBean傻傻分不清楚

阿豪說
學到了?請我喝杯咖啡吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章