《Spring揭秘》---- 工厂方法与FactoryBean

Posted coder为

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Spring揭秘》---- 工厂方法与FactoryBean相关的知识,希望对你有一定的参考价值。

 

  面向接口编程时,虽然对象可以通过声明接口来避免对特定接口实现类的过渡耦合,但总归需要一种方式将生命依赖接口的对象与接口实现类关联起来。

  问题背景:

public class Foo {
    private BarInterface barInterface;
    public Foo() {
        barInstance = new BarInterfaceImpl();
    }
}

  这样接口与实现类的耦合性很高。

  如果BarInterfaceImpl类是我们设计开发的,可以直接通过依赖注入,让容器帮助我们解除接口与实现类的耦合性。但是,有时候我们需要依赖第三方库,需要实例化并使用第三方库中的相关类,可以通过xml方式配置,但却没法使用注解方式。

  通常做法是通过使用工厂方法模式,提供一个工厂类来实例化具体的接口实例类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何改动。

  1.  静态工厂方法

假设某个第三方库发布了BarInterface,为了向使用该接口的客户端对象屏蔽以后可能对BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类:

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

使用以下配置方式,将该静态工厂方法返回的实现注入Foo.

<bean id="foo" class="...Foo">
    <property name="barInterface">
        <ref bean="bar"/>
    </property>
</bean>

<bean id="bar" class="...StaticBarInterfaceFactory" factory-method="getInstance"/>

class指定静态工厂方法类,factory-method指定工厂方法名称,然后容器调用该静态工厂方法类的指定工厂方法,并返回方法调用后的结果,即BarInterfaceImpl的实例。

某些时候,有的工厂类的工厂方法可能需要参数来返回相应的实例,可以通过<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定义,使用<constructor-arg>传入的是工厂方法的参数,而不是静态工厂方法实现类的构造方法的参数(静态工厂方法实现类页没有提供显式的构造方法)

  

  2. 非静态工厂方法

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的定义需要使用factory-bean来指定工厂方法所在的工厂类实例,而不是通过class属性来指定工厂方法所在类的类型。指定工厂方法名还是使用factory-method指定。如果非静态工厂方法调用时也需要提供参数的话,处理方式是与静态工厂方法相似的,都可以通过<constructor-arg>来指定方法调用参数。

 

  3. FactoryBean

当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现org.spring-framework.beans.factory.FactoryBean接口,给出自己的对象实例化逻辑代码,当然,不使用FactoryBean,而像通常那样实现自定义的工厂方法类也是可以的。FactoryBean接口定义如下:

public interface FactoryBean {

    Object getObject() throws Exception;
    Class getObjectType();
    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;
    }
}        
public class NextDayDateDisplayer
{
    private DateTime dateOfNextDay;
    // 相应的setter方法
    // ...
}
<bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer">
    <property name="dateOfNextDay">
        <ref bean="nextDayDate"/>
    </property>
</bean>
<bean id="nextDayDate" class="...NextDayDateFactoryBean">
</bean>

NextDayDateDisplayer所声明的dateOfNextDay的类型为DateTime, 而不是NextDayDateFactoryBean.也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所生产的对象类型,而非FactoryBean实现本身。如果一定要取得FactoryBean本身的话,可以通过在bean定义的id之前加&来达到目的,如:

Object factoryBean = container.getBean("&nextDayDate");

 

以上是关于《Spring揭秘》---- 工厂方法与FactoryBean的主要内容,如果未能解决你的问题,请参考以下文章

spring常见设计模式之工厂模式与外观模式

Factory Method模式

揭秘设计模式之工厂模式

Spring Boot 揭秘与实战 附录 - Spring Boot 公共配置

Spring IOC与工厂模式

Spring Boot 揭秘与实战 源码分析 - 工作原理剖析