Spring Framework 元件註冊 之 FactoryBean

發表於2019-07-04

Spring Framework 元件註冊 之 FactoryBean

前言

前兩篇文章介紹瞭如何使用@Component@Import註解來向spring容器中註冊元件(javaBean),本文將介紹通過FactoryBean介面繼續向spring容器中註冊元件。可能第一印象是spring中BeanFactory介面,但是這裡確實說的是FactoryBean

推薦閱讀

FactoryBean 與 BeanFactory

根據介面名稱,我們也可以簡單看出兩者的區別

  • FactoryBean:它是spring中的一個Bean,只不過它是一個特殊的Bean(工廠Bean),我們可以通過它來自定義生產需要的普通JavaBean
  • BeanFactory:它是spring的Bean工廠,是spring最為重要的介面之一,spring通過此介面獲取,管理容器中的各個Bean

接下來將進入本文正題,如何通過FactoryBean介面向spring容器中註冊元件

FactoryBean簡單使用

正如前面說的,FactoryBean也是spring中的一個Bean,但是它又是一個特殊的Bean,它的存在是為了生產其他的JavaBean。首先我們看看FactoryBean自身的介面定義

public interface FactoryBean<T> {
    /**
     * 從Spring容器中獲取Bean時會呼叫此方法,返回一個T物件
     */
    @Nullable
    T getObject() throws Exception;

    /**
     * 此工廠Bean返回物件的型別
     */
    @Nullable
    Class<?> getObjectType();

    /**
     * 工廠Bean建立的物件是否為單例,
         * 如果返回false,說明getObject方法的例項物件不是單例的,
     * Spring每次從容器中獲取T物件時,都呼叫getObject方法建立一個物件
     */
    default boolean isSingleton() {
        //spring 5 介面預設返回true(單例)
        return true;
    }
}

FactoryBean介面定義簡單明瞭,就是用來獲取一個Bean的基本資訊,下面我們自己實現該介面,來生產一個javaBean

/**
 * 產生 Bike 物件的工廠Bean
 */
@Component
public class BikeFactoryBean implements FactoryBean<Bike> {

    public Bike getObject() throws Exception {
        System.out.println("......開始建立Bike物件......");
        return new Bike();
    }

    public Class<?> getObjectType() {
        return Bike.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

自定義的一個JavaBean類

/**
 * 待註冊的自定義元件
 */
@Data
public class Bike {
    private String id = "by FactoryBean";
}

新增spring容器啟動的引導類

/**
 * spring 容器啟動引導類,測試 FactoryBean 功能
 */
@ComponentScan("com.spring.study.ioc.factorybean")
public class TestFactoryBeanBootstrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestFactoryBeanBootstrap.class);
        //獲取工廠Bean本身的Id
        String[] beanNames = applicationContext.getBeanNamesForType(BikeFactoryBean.class);
        System.out.println("BikeFactoryBean names:" + Arrays.asList(beanNames));
        //獲取工廠Bean產生的Bean的Id
        beanNames = applicationContext.getBeanNamesForType(Bike.class);
        System.out.println("Bike bean names:" + Arrays.asList(beanNames));
        Object bean = applicationContext.getBean("bikeFactoryBean");
        System.out.println(bean);
        bean = applicationContext.getBean(Bike.class);
        System.out.println(bean);
        // 獲取工廠Bean 本身的例項物件
        bean = applicationContext.getBean(BeanFactory.FACTORY_BEAN_PREFIX + "bikeFactoryBean");
        System.out.println(bean);
        applicationContext.close();
    }
}

啟動spring容器,控制檯列印結果:

BikeFactoryBean names:[&bikeFactoryBean]
Bike bean names:[bikeFactoryBean]
......開始建立Bike物件......
Bike(id=by FactoryBean)
Bike(id=by FactoryBean)
com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003

由結果可以看出

  • 雖然程式碼中只在BikeFactoryBean類上加了@Component註解,但是從spring容器仍然可以獲取到Bike類的資訊
  • 工廠Bean的Id與實際產生的Bean的Id僅差了一個&符,也就是說,工廠Bean定義的Id實際為getObject()方法返回Bean的Id,而工廠Bean本身的Id被新增了一個字首&
  • 工廠Bean的isSingleton()方法返回了true,所以通過spring容器多次獲取實際的Bean時,getObject()方法也是執行一次
  • 根據工廠Bean的Id可以看出,要想從spring容器中獲取工廠Bean本身,則需要在註冊的Id前面新增一個&符,而此字首在BeanFactory介面中已經定義了FACTORY_BEAN_PREFIX

如果將BikeFactoryBeanisSingleton()方法返回了false

 public boolean isSingleton() {
        return false;
 }

重新啟動spring容器,可以看如下結果:

BikeFactoryBean names:[&bikeFactoryBean]
Bike bean names:[bikeFactoryBean]
......開始建立Bike物件......
Bike(id=by FactoryBean)
......開始建立Bike物件......
Bike(id=by FactoryBean)
com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003

由結果可以看出,唯一的變化在於從spring容器中多次獲取實際Bean時,工廠Bean的getObject()方法被多次進行了呼叫。這與spring容器中被標識為原型的普通Bean相同,每次從spring中獲取Bean時都會被例項化。

FactoryBean 執行過程

要想了解FactoryBean的執行過程,就需要結合Spring容器啟動的過程來進行分析。而spring容器的啟動過程經過了紛繁複雜的步驟。為了儘可能少的入坑和挖坑,下面僅結合FactoryBean相關的原始碼進行說明。

在開始入坑之旅之前,結合前面的例子做幾點說明,方便後續講解

前面定義的 BikeFactoryBean 類上面直接新增了@Component註解,這樣spring會預設以類名首字母小寫(bikeFactoryBean)作為beanName;如果使用@Bean進行註冊時,spring預設會以方法名作為beanName,下面繼續以“BikeFactoryBean”為例。

  1. spring容器啟動過程中,在執行完所有的BeanFactoryPostProcessorBeanPostProcessor以及註冊Listener後會執行org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization()方法,此方法會對剩下所有的非Abstract非LazyInit的單例項Bean進行例項化,以下為部分程式碼片段。
@Override
public void preInstantiateSingletons() throws BeansException {
        ...省略程式碼...
    // 1. 拷貝一份副本:spring容器中的所有的Bean名稱
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
        // 2. 遍歷每一個beanName,嘗試通過getBean()方法進行例項化
        // 在getBean()方法內部會先嚐試從容器singletonObjects中獲取Bean,如果沒有才會進行例項化操作
        for (String beanName : beanNames) {
            // 3. 通過beanName獲取Bean定義資訊 BeanDefinition
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        // 4. 根據BeanDefinition判斷該Bean是否不是抽象的,單例的,非懶載入的
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
        // 5. 滿足上面的條件後,在根據beanName判斷此Bean是否是一個工廠Bean(實現了FactoryBean介面)
                if (isFactoryBean(beanName)) {
            // 6. 如果是一個工廠Bean,則在此處進行工廠Bean本身的例項化
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
            ...省略程式碼...
                }
                else {
            // 如果不是工廠bean,也是呼叫getBean()方法進行例項化
                    getBean(beanName);
                }
            }
        }
        ...省略程式碼...
    }

preInstantiateSingletons()是在finishBeanFactoryInitialization()方法內部呼叫的

根據上面的程式碼流程,在第6步時,會對BikeFactoryBean類本身進行例項化,並且可以看出傳遞的beanName為初始註冊的name前新增了&符字首即&bikeFactoryBean,用於在genBean()方法內部標識它是一個工廠Bean。但是在跟蹤原始碼後發現,在getBean()方法內部,會先將傳入的beanName(&bikeFactoryBean)開頭的&符去除,並且最終例項化Bean後,在容器中儲存的beanName還是不帶&符字首的名稱即bikeFactoryBean

  1. 根據第一步的結果,spring容器在啟動後,工廠Bean會像普通Bean一樣在spring容器中會保留一條自身的單例項Bean(spring容器中儲存的資料為:<bikeFactoryBean, BikeFactoryBean>),既然spring容器中只儲存了BikeFactoryBean本身,那麼後續獲取Bike類的beanName和Bean例項時,又是怎麼獲取到的呢?帶著疑問,我們繼續看後面的程式碼。首先,上面的例子中呼叫了applicationContext.getBeanNamesForType(Bike.class)方法來獲取Bike類的beanName。所以繼續跟蹤此方法看看到底發生了什麼。
    // getBeanNamesForType()方法內部最終呼叫了此方法,可斷點跟蹤至此
    private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
        // 用來儲存符合條件的結果(beanName集合)
        List<String> result = new ArrayList<>();
        // 與上面的程式碼相似,遍歷spring容器中註冊的所有的beanNames
        for (String beanName : this.beanDefinitionNames) {
            if (!isAlias(beanName)) {
                try {
                    // 根據beanName獲取Bean的定義資訊 BeanDefinition
                    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                    // 根據BeanDefinition 進行檢查
                    if (!mbd.isAbstract() && (allowEagerInit ||
                            (mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
                                    !requiresEagerInitForType(mbd.getFactoryBeanName()))) {
                        // 根據beanName和Bean的定義資訊判斷是否是工廠Bean
                        boolean isFactoryBean = isFactoryBean(beanName, mbd);
                        BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
                        boolean matchFound =
                                (allowEagerInit || !isFactoryBean ||
                                        (dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
                                        (includeNonSingletons ||
                                                (dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
                                        // 根據Bean的定義資訊判斷完後,在此方法中判斷此beanName對應的Bean例項是否與傳入的型別相匹配
                                        isTypeMatch(beanName, type);
                        // 如果根據beanName獲得的是一個工廠Bean,並且與傳入的型別不匹配,則滿足條件,將beanName新增 & 符字首
                        if (!matchFound && isFactoryBean) {
                            // 對於工廠Bean,接下來嘗試匹配工廠Bean例項本身
                            beanName = FACTORY_BEAN_PREFIX + beanName;
                            matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
                        }
                        // 如果獲取的Bean例項與傳入的型別匹配,將beanName新增到結果集合中
                        if (matchFound) {
                            result.add(beanName);
                        }
                    }
                }
                catch (CannotLoadBeanClassException ex) {
                    // ...省略程式碼...
                }
                catch (BeanDefinitionStoreException ex) {
                    // ...省略程式碼...
                }
            }
        }
        // ...省略程式碼...

        return StringUtils.toStringArray(result);
    }

isTypeMatch()方法中的部分程式碼

public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
        // 對beanName進行處理,將開頭的 & 符過濾
        String beanName = transformedBeanName(name);
        // 從spring容器中獲取單例項Bean,由於spring容器啟動時已經將單例項Bean進行了例項化,
        // 所以此時可以直接在容器中得到Bean例項
        Object beanInstance = getSingleton(beanName, false);
        if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
            // 獲取到Bean例項後,判斷是否為工廠Bean
            if (beanInstance instanceof FactoryBean) {
                // 如果是工廠Bean,並且獲取的beanName不是以&符開頭
                if (!BeanFactoryUtils.isFactoryDereference(name)) {
                    // 將例項強轉為 FactoryBean 並呼叫 FactoryBean介面的getObjectType()方法,
                    // 獲取工廠Bean所生產的例項型別
                    Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
                    // 判斷工廠Bean生產的例項型別與傳入的型別是否匹配
                    return (type != null && typeToMatch.isAssignableFrom(type));
                }
                else {
                    return typeToMatch.isInstance(beanInstance);
                }
            }
            // ...省略程式碼...
        }
        // ...省略程式碼...
    }

getTypeForFactoryBean()方法中的程式碼

protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) {
        try {
            if (System.getSecurityManager() != null) {
                return AccessController.doPrivileged((PrivilegedAction<Class<?>>)
                        factoryBean::getObjectType, getAccessControlContext());
            }
            else {
                // 直接呼叫 FactoryBean 介面的 getObjectType()方法,獲取生產的型別
                return factoryBean.getObjectType();
            }
        }
        catch (Throwable ex) {
            // Thrown from the FactoryBean's getObjectType implementation.
            logger.info("FactoryBean threw exception from getObjectType, despite the contract saying " +
                    "that it should return null if the type of its object cannot be determined yet", ex);
            return null;
        }
    }

以上便是getBeanNamesForType()方法經過的部分重要程式碼

由此可以看出,當我們想要獲取BikeFactoryBean本身的beanName時,doGetBeanNamesForType方法內部將bikeFactoryBean前新增了&符字首,於是便獲取到了&bikeFactoryBean
當我們想要獲取Bike型別的beanName時,spring會通過容器遍歷已經註冊的所有的beanNames,然後根據beanName及對應的Bean定義資訊BeanDefinition進行判斷過濾,並且對於所有的工廠Bean,會獲取spring容器中已經例項化的Bean物件,呼叫 FactoryBean 介面的 getObjectType()方法,得到工廠Bean所生產的例項型別,然後與Bike.class相比較,如果匹配,則將此beanName儲存到結果集中,最後返回。所以,當我們想要獲取Bike型別的beanName時,從spring容器中便可以找到bikeFactoryBean

  1. 從spring容器中獲取到beanName後,我們繼續獲取Bike例項
    從前文中引導類的程式碼可以看出,獲取Bike例項有兩種方式,跟蹤原始碼可以發現,根據Bike型別獲取例項時,spring實際是通過第二步獲取到beanName後再最終呼叫doGetBean方法獲取例項物件。下面看看部分原始碼
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        // 對傳入的beanName進行過濾,去除&符字首
        final String beanName = transformedBeanName(name);
        Object bean;

        // 從spring容器中獲取例項,由於spring容器啟動時已經將單例項Bean進行例項化,所以此時可以直接獲得
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isTraceEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            // 獲取指定的Bean例項,如果是工廠bean,則為Bean例項本身或其建立的物件。
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        // ...省略程式碼...
        return (T) bean;
    }

從上面的程式碼可以看出,獲取Bike例項的具體程式碼還在getObjectForBeanInstanc()方法內部,我們繼續檢視

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

        // 判斷beanName是否是以&符開頭的
        if (BeanFactoryUtils.isFactoryDereference(name)) {
            if (beanInstance instanceof NullBean) {
                return beanInstance;
            }
            if (!(beanInstance instanceof FactoryBean)) {
                throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
            }
        }

        // 根據beanName從spring容器中獲取的Bean例項如果不是工廠Bean,或者beanName是以&符開頭,就直接返回這個Bean例項
        // 當我們獲取Bike型別的例項時,beanName為“bikeFactoryBean”,
        // beanInstance為“BikeFactoryBean”型別,是一個工廠Bean,所以條件不滿足,繼續向下走
        if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
            return beanInstance;
        }

        Object object = null;
        if (mbd == null) {
            // 根據beanName從快取中獲取Bean例項,第一次來獲取Bike例項時為空,
        // factoryBeanObjectCache.get(beanName);
            // 後續再獲取時,便可以在此獲得到,然後返回
            object = getCachedObjectForFactoryBean(beanName);
        }
        if (object == null) {
            // 將獲取的工廠Bean強轉為 FactoryBean 型別,以便下面呼叫其getObject()方法獲取物件
            FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
            // 獲取bean定義資訊
            if (mbd == null && containsBeanDefinition(beanName)) {
                mbd = getMergedLocalBeanDefinition(beanName);
            }
            boolean synthetic = (mbd != null && mbd.isSynthetic());
            //在此方法內部呼叫 FactoryBean 介面的 getObject()方法獲取物件
            object = getObjectFromFactoryBean(factory, beanName, !synthetic);
        }
        return object;
    }

距離真相還差兩步了,堅持就是勝利,我們繼續看getObjectFromFactoryBean()的原始碼

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
        // 此處呼叫 FactoryBean 的isSingleton()方法,判斷是否是一個單列
        // 如果是單例的,走if內部,獲取到物件後,會儲存到factoryBeanObjectCache快取中,以便後續使用
        if (factory.isSingleton() && containsSingleton(beanName)) {
            synchronized (getSingletonMutex()) {
                // 檢查快取中是否已經存在
                Object object = this.factoryBeanObjectCache.get(beanName);
                if (object == null) {
                    // 呼叫最後一個方法,執行FactoryBean 的 getObject()方法獲取物件
                    object = doGetObjectFromFactoryBean(factory, beanName);
                    // 再次檢查快取
                    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                    if (alreadyThere != null) {
                        object = alreadyThere;
                    }
                    else {
                        // ... 省略程式碼 ...
                        if (containsSingleton(beanName)) {
                            // 將獲取的物件放入factoryBeanObjectCache快取中,以便後續使用
                            this.factoryBeanObjectCache.put(beanName, object);
                        }
                    }
                }
                return object;
            }
        }
        // 如果不是單例的,每次獲取的物件直接返回,不會放入快取中,所以每次都會呼叫getObject()方法
        else {
            Object object = doGetObjectFromFactoryBean(factory, beanName);
            if (shouldPostProcess) {
                try {
                    object = postProcessObjectFromFactoryBean(object, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
                }
            }
            return object;
        }
    }

根據上面的流程,終於來到了最後一步

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((PrivilegedExceptionAction<Object>) factory::getObject, acc);
                } catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                // 直接呼叫 FactoryBean 介面的 getObject()方法獲取例項物件
                object = factory.getObject();
            }
        }
        catch (FactoryBeanNotInitializedException ex) {
            throw new BeanCurrentlyInCreationException(beanName, ex.toString());
        }
        // ... 省略程式碼 ...
        return object;
    }

經過如此多的程式碼,spring終於幫我們獲取到Bike物件例項

通過BikeFactoryBean來獲取Bike類的例項時,spring先獲取Bike型別對應的beanName(bikeFactoryBean),然後根據beanName獲取到工廠Bean例項本身(BikeFactoryBean),最終spring會呼叫BikeFactoryBean 的 getObject()方法來獲取Bike物件例項。並且根據 BikeFactoryBean 例項的 isSingleton() 方法來判斷Bike型別的例項是否時單例的,依此來決定要不要將獲取的Bike物件放入到快取中,以便後續使用。

總結

本文主要講解了如何通過 FactoryBean介面向spring容器中注入元件,通過簡單的案例進行模擬,並根據案例對原始碼的執行過程進行跟蹤,分析了FactoryBean介面的執行過程。
另外,在每一次跟蹤spring原始碼時,都會有新的收穫。在spring龐大的體系下,只有定位好自己的目標,明確自己的需求,才不會被spring無限的程式碼所淹沒。

學習永遠都不是一件簡單的事情,可以有迷茫,可以懶惰,但是前進的腳步永遠都不能停止。

不積跬步,無以至千里;不積小流,無以成江海;

相關文章