面向接口编程时,虽然对象可以通过声明接口来避免对特定接口实现类的过渡耦合,但总归需要一种方式将生命依赖接口的对象与接口实现类关联起来。
问题背景:
public class Foo {
private BarInterface barInterface;
public Foo() {
barInstance = new BarInterfaceImpl();
}
}
这样接口与实现类的耦合性很高。
如果BarInterfaceImpl类是我们设计开发的,可以直接通过依赖注入,让容器帮助我们解除接口与实现类的耦合性。但是,有时候我们需要依赖第三方库,需要实例化并使用第三方库中的相关类,可以通过xml方式配置,但却没法使用注解方式。
通常做法是通过使用工厂方法模式,提供一个工厂类来实例化具体的接口实例类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何改动。
- 静态工厂方法
假设某个第三方库发布了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");