Spring中老生常談的FactoryBean
本文完整程式碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean
FactoryBean
和BeanFactory
由於在命名上極其相似,一直以來困擾了不少的開發者。
BeanFactory
,耳熟能詳的Spring核心介面,提供IoC容器的最基本功能。但要解釋FactoryBean
一句話可能就說不清楚了。我們將從下面的例子逐步說明,FactoryBean是什麼,它提供了什麼樣的能力。
/** * 布料 * 包含顏色屬性 * Created by OKevin On 2019/9/3 **/ public class Cloth { private Red red; //省略setter/getter方法 }
當初始化一個Cloth物件時,我希望Red物件也被賦值,此時我將在Cloth的構造方法中new一個Red物件。
public Cloth() {
red = new Red();
}
但是隨著業務的發展,我希望Cloth的顏色屬性將是Blue藍色,這時我將修改程式碼將Red和Blue類抽象出一個Color介面,Cloth程式碼將重構:
/** * 布料 * 包含顏色屬性 * Created by OKevin On 2019/9/3 **/ public class Cloth { private Color color; public Cloth() { color = new Blue(); } //省略setter/getter方法 }
業務又進一步發展,Cloth類中的顏色屬性將會根據一定的條件賦值為Red紅色,此時我們將程式碼繼續重構:
/** * 布料 * 包含顏色屬性 * Created by OKevin On 2019/9/3 **/ public class Cloth { private Color color; public Cloth() { if (condition()) { color = new Blue(); } else { color = new Red(); } } //省略setter/getter方法 }
這樣的程式碼的確能執行,但如果有新的條件繼續加入到業務中,此時我們又將改動Cloth類的構造方法,而我們認為Cloth方法是一個比較核心的業務物件,不應該經常對它進行修改,並且在構造方法中對於Color物件建立過於冗餘,不符合單一職責的原則,所以我們將Color物件的建立過程通過工廠方法模式來完成。
靜態工廠方法
我們再次將Cloth類進行如下重構(為了使示例程式碼更加簡潔,下面的示例將只建立Red物件):
/**
* 布料
* 包含顏色屬性
* Created by OKevin On 2019/9/3
**/
public class Cloth {
private Color color;
public Cloth() {
color = StaticColorFactory.newInstance();
}
//省略setter/getter方法
}
/**
* 靜態工廠方法
* Created by OKevin On 2019/9/3
**/
public class StaticColorFactory {
public static Color getInstance() {
return new Red();
}
}
如果我們在Spring容器中要通過靜態工廠方法,建立具體的物件例項應該怎麼做呢?
眾所周知,要將一個物件例項交由Spring容器管理,我們通常是通過以下XML配置:
<bean id="cloth" class="com.coderbuff.bean.Cloth">
<property name="color" ref="red"/>
</bean>
<bean id="red" class="com.coderbuff.bean.Red" />
但此時,Red物件例項並不是由Spring容器管理,而是由靜態工廠方法建立的,此時我們應該講XML配置修改為以下方式:
<bean id="cloth" class="com.coderbuff.bean.Cloth">
<property name="color" ref="red"/>
</bean>
<bean id="red" class="com.coderbuff.factory.StaticColorFactory" factory-method="getInstance" />
這是Spring支援靜態工廠方法建立物件例項的特定方式。這樣我們就能在Spring中通過靜態工廠方法建立物件例項。
例項工廠方法
有靜態工廠方法,就有非靜態工廠方法,區別就是方法不是靜態的。
/**
* 例項工廠方法
* Created by OKevin On 2019/9/3
**/
public class ColorFactory {
public Color getInstance() {
return new Red();
}
}
例項工廠方法在Spring中XML配置略有不同:
<bean id="cloth" class="com.coderbuff.bean.Cloth">
<property name="color" ref="red"/>
</bean>
<bean id="colorFactory" class="com.coderbuff.factory.ColorFactory"/>
<bean id="red" factory-bean="colorFactory" factory-method="getInstance"/>
通過配置可以看到,我們需要首先在Spring中例項化工廠,再通過工廠物件例項化Red物件。
在有了對工廠方法在Spring中建立物件例項的認識後,FactoryBean實際上就是為我們簡化這個操作。下面我們將通過FactoryBean來建立Red物件。
FactoryBean
/**
* Created by OKevin On 2019/9/3
**/
public class ColorFactoryBean implements FactoryBean<Color> {
public Color getObject() throws Exception {
return new Red();
}
public Class<?> getObjectType() {
return Red.class;
}
public boolean isSingleton() {
return false;
}
}
通過實現FactoryBean的方式,XML配置如下:
<bean id="cloth" class="com.coderbuff.bean.Cloth">
<property name="color" ref="red"/>
</bean>
<bean id="red" class="com.coderbuff.factory.ColorFactoryBean"/>
這樣就不用像工廠方法那樣配置相應的屬性,直接按照普通的Bean注入即可,由於Spring內部做了特殊處理,此時名稱為“red”的Bean並不是ColorFactoryBean,而是它方法中getObject中返回的物件。如果實在想要獲取ColorFactoryBean的物件例項,則在Bean的名稱前加入“&”即可(“&red”)。
看到這裡,是否對FactoryBean有了一點認識呢?FactoryBean在Spring中最為典型的一個應用就是用來建立AOP的代理物件。
我們知道AOP實際上是Spring在執行時建立了一個代理物件,也就是說這個物件,是我們在執行時建立的,而不是一開始就定義好的,這很符合工廠方法模式。更形象地說,AOP代理物件通過Java的反射機制,在執行時建立了一個代理物件,在代理物件的目標方法中根據業務要求織入了相應的方法。這個物件在Spring中就是——ProxyFactoryBean。
ProxyFactoryBean
我們將通過比較“古老”的方式建立一個Red物件的切面,在它的print方法執行前和執行後分別執行一條語句。之所以古老是因為我們往往通過註解的方式,而不會這麼折磨自己去寫一個切面物件。
/**
* 環繞通知
* Created by OKevin On 2019/9/4
**/
public class LogAround implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("呼叫目標方法【前】列印日誌");
Object result = invocation.proceed();
System.out.println("呼叫目標方法【後】列印日誌");
return result;
}
}
此時我們需要ProxyFactoryBean的介入為我們建立一個代理物件並由Spring容器管理,根據上面ColorFactoryBean的經驗,ProxyFacoryBean也應該如下配置:
<bean id="xxx" class="org.springframework.aop.framework.ProxyFactoryBean" />
答案是肯定的,只是ProxyFactoryBean多了幾個引數,既然是生成代理物件,那麼目標物件、目標方法就必不可少,實際的XLM配置如下:
<bean id="logAround" class="com.coderbuff.aop.LogAround"/>
<bean id="red" class="com.coderbuff.bean.Red"/>
<bean id="proxyRed" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.coderbuff.bean.Color"/>
<property name="interceptorNames" value="logAround"/>
<property name="target" ref="red"/>
<property name="proxyTargetClass" value="true"/>
</bean>
通過測試程式,ProxyFactoryBean的確生成了一個代理物件。
public class ProxyFactoryBeanTest {
private ClassPathXmlApplicationContext ctx;
@Before
public void init() {
ctx = new ClassPathXmlApplicationContext("spring-proxyfactorybean.xml");
}
@Test
public void testProxyFactory() {
Red proxyRed = (Red) ctx.getBean("proxyRed");
proxyRed.print();
}
}
所以,FactoryBean為我們例項化Bean提供了一個更為靈活的方式,我們可以通過FactoryBean創建出更為複雜的Bean例項。
本文完整程式碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean
這是一個能給程式設計師加buff的公眾號 (CoderBuff)