Spring中老生常談的FactoryBean


本文完整代碼地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean

FactoryBeanBeanFactory由於在命名上極其相似,一直以來困擾了不少的開發者。

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM