Spring bean 之 FactoryBean

Spring中有兩種類型的Bean,一種是普通Bean,另一種是工廠Bean,即FactoryBean。Spring FactoryBean是創(chuàng)建復(fù)雜的bean,一般的bean直接用xml配置即可,如果一個(gè)bean的創(chuàng)建過程中涉及到很多其他的bean和復(fù)雜的邏輯,用xml配置比較困難,這時(shí)可以考慮用FactoryBean.
這兩種Bean都被容器管理,但工廠Bean跟普通Bean不同,其返回的對象不是指定類的一個(gè)實(shí)例,其返回的是該FactoryBean的getObject方法所返回的對象罚缕。在Spring框架內(nèi)部,有很多地方有FactoryBean的實(shí)現(xiàn)類,它們在很多應(yīng)用如(Spring的AOP蚊夫、ORM衣洁、事務(wù)管理)及與其它第三框架(ehCache)集成時(shí)都有體現(xiàn),下面簡單分析FactoryBean的用法呀狼。

1攒读、FactoryBean用法

1)實(shí)現(xiàn)FactoryBean接口

/**
 * Created by Carl on 2016/8/12.
 */
public class FactoryBeanTest implements FactoryBean<Object> {

    private boolean flag;

    public void setFlag(boolean flag){
        this.flag = flag;
    }

    // 返回這個(gè)Bean的實(shí)例
    @Override
    public Object getObject() throws Exception {
        return flag ? "carl" : new Date();
    }

    // 返回這個(gè)類類型
    @Override
    public Class<?> getObjectType() {
        return flag ? String.class : Date.class;
    }

    // 是否為單例
    @Override
    public boolean isSingleton() {
        return true;
    }
}

2)配置XML將Bean納入Spring管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="factoryBeanTest1" class="com.weimob.carl.user.dto.FactoryBeanTest">
        <property name="flag" value="true" />
    </bean>

    <bean id="factoryBeanTest2" class="com.weimob.carl.user.dto.FactoryBeanTest">
        <property name="flag" value="false" />
    </bean>

</beans>

3)Test

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application-test.xml");
        String string = context.getBean("factoryBeanTest1", String.class);
        Date date = context.getBean("factoryBeanTest2", Date.class);
        System.out.println(string);
        System.out.println(date);
    }

}

通過簡單的測試可知,該類輸出如下:


這里寫圖片描述

2绸贡、實(shí)現(xiàn)原理

這里寫圖片描述

大家都知道應(yīng)該知道BeanFactory在Spring IOC中的作用.它定義了Spring容器的基本方法加袋。其中就包含getBean.由上面的方法調(diào)用圖我們就可以看到BeanFactory與FactoryBean的關(guān)系凛辣。下面我們具體看一看代碼實(shí)現(xiàn):
1)org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance

protected Object getObjectForBeanInstance(
        Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

    // 如果這里不是對FactoryBean的調(diào)用,那么結(jié)束處理
    if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
        throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
    }

    // Now we have the bean instance, which may be a normal bean or a FactoryBean.
    // If it's a FactoryBean, we use it to create a bean instance, unless the
    // caller actually wants a reference to the factory.
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }

    Object object = null;
    if (mbd == null) {
        object = getCachedObjectForFactoryBean(beanName);
    }
    if (object == null) {
        // Return bean instance from factory.
        FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
        // Caches object obtained from FactoryBean if it is a singleton.
        if (mbd == null && containsBeanDefinition(beanName)) {
            mbd = getMergedLocalBeanDefinition(beanName);
        }
        boolean synthetic = (mbd != null && mbd.isSynthetic());
        // 這里從FactoryBean中得到Bean
        object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
}

2)org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            // 從cache中獲取這個(gè)對象
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                // 從FactoryBean獲取這個(gè)對象
                object = doGetObjectFromFactoryBean(factory, beanName);
                // Only post-process and store if not put there already during getObject() call above
                // (e.g. because of circular reference processing triggered by custom getBean calls)
                Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                if (alreadyThere != null) {
                    object = alreadyThere;
                }
                else {
                    if (object != null && shouldPostProcess) {
                        try {
                            object = postProcessObjectFromFactoryBean(object, beanName);
                        }
                        catch (Throwable ex) {
                            throw new BeanCreationException(beanName,
                                    "Post-processing of FactoryBean's singleton object failed", ex);
                        }
                    }
                    this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
                }
            }
            return (object != NULL_OBJECT ? object : null);
        }
    }
    else {
        // 從FactoryBean獲取這個(gè)對象
        Object object = doGetObjectFromFactoryBean(factory, beanName);
        if (object != null && shouldPostProcess) {
            try {
                object = postProcessObjectFromFactoryBean(object, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
            }
        }
        return object;
    }
}

3)org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean

private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
        throws BeanCreationException {
    Object object;
    try {
        if (System.getSecurityManager() != null) {
            AccessControlContext acc = getAccessControlContext();
            try {
                object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                    @Override
                    public Object run() throws Exception {
                            // 最終調(diào)用FactoryBean.getObject()方法
                            return factory.getObject();
                        }
                    }, acc);
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            // 最終調(diào)用FactoryBean.getObject()方法
            object = factory.getObject();
        }
    }
    catch (FactoryBeanNotInitializedException ex) {
        throw new BeanCurrentlyInCreationException(beanName, ex.toString());
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
    }

    // Do not accept a null value for a FactoryBean that's not fully
    // initialized yet: Many FactoryBeans just return null then.
    if (object == null && isSingletonCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(
                beanName, "FactoryBean which is currently in creation returned null from getObject");
    }
    return object;
}

現(xiàn)在大家是不是對FactoryBean與BeanFactory這2個(gè)在Spring中非常重要的2個(gè)對象理解的很清楚了。

3职烧、FactoryBean的存在價(jià)值

上面返回的已經(jīng)是作為工廠的FactoryBean生產(chǎn)的產(chǎn)品,而不是FactoryBean本身扁誓。這種FactoryBean的機(jī)制可以為我們提供一個(gè)很好的封裝機(jī)制,比如封裝Proxy、RMI/JNDI等蚀之。通過對FactoryBean實(shí)現(xiàn)過程的原理進(jìn)行分析蝗敢,相信大家會(huì)對getObject有很深刻的印象。這個(gè)方法就是主要的FactoryBean的接口足删,需要實(shí)現(xiàn)特定的工廠的生產(chǎn)過程寿谴,至于這個(gè)生產(chǎn)過程是怎么和IoC容器整合的,就是在上面的分析的內(nèi)容失受。

4讶泰、FactoryBean與設(shè)計(jì)模式

下圖是一個(gè)典型的工廠模式的UML圖。在這里我們可以看看設(shè)計(jì)模式中的工廠模式拂到,做一個(gè)對比痪署,以加深對這些代碼的理解。


這里寫圖片描述

對比兩者的實(shí)現(xiàn),可以看到FactoryBean類似于AbstractFactory抽象工廠,getObjectForBeanInstance()方法類似于createProductA()這樣的生產(chǎn)接口塞祈,而具體的FactoryBean實(shí)現(xiàn),如TransactionProxyFactoryBean辜王,就是具體的工廠實(shí)現(xiàn),其生成出的TransactionProxy就是"抽象工廠"模式對應(yīng)的ConcreteProduct.有了抽象工廠設(shè)計(jì)模式的參考與對比。對FactoryBean的設(shè)計(jì)和實(shí)現(xiàn)就更容易理解一些了罐孝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呐馆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子莲兢,更是在濱河造成了極大的恐慌汹来,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件改艇,死亡現(xiàn)場離奇詭異收班,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谒兄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門摔桦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事邻耕∨缚В” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵兄世,是天一觀的道長啼辣。 經(jīng)常有香客問我,道長御滩,這世上最難降的妖魔是什么鸥拧? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮削解,結(jié)果婚禮上住涉,老公的妹妹穿的比我還像新娘。我一直安慰自己钠绍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布花沉。 她就那樣靜靜地躺著柳爽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碱屁。 梳的紋絲不亂的頭發(fā)上磷脯,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機(jī)與錄音娩脾,去河邊找鬼赵誓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柿赊,可吹牛的內(nèi)容都是我干的俩功。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碰声,長吁一口氣:“原來是場噩夢啊……” “哼诡蜓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胰挑,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蔓罚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后瞻颂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豺谈,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年贡这,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茬末。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藕坯,死狀恐怖团南,靈堂內(nèi)的尸體忽然破棺而出噪沙,到底是詐尸還是另有隱情,我是刑警寧澤吐根,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布正歼,位于F島的核電站,受9級特大地震影響拷橘,放射性物質(zhì)發(fā)生泄漏局义。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一冗疮、第九天 我趴在偏房一處隱蔽的房頂上張望萄唇。 院中可真熱鬧,春花似錦术幔、人聲如沸另萤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽四敞。三九已至,卻和暖如春拔妥,著一層夾襖步出監(jiān)牢的瞬間忿危,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工没龙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铺厨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓硬纤,卻偏偏與公主長得像解滓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子咬摇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容