Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

在上篇文章中咱們已經對容器的第一個擴展點(BeanFactoryPostProcessor)作了一系列的介紹。其中主要介紹了Spring容器中BeanFactoryPostProcessor的執行流程。已經Spring自身利用了BeanFactoryPostProcessor完成了什麼功能,對於一些細節問題可能說的不夠仔細,可是在當前階段我想要作的主要是爲咱們之後學習源碼打下基礎。因此對於這些問題咱們暫且不去過多糾結,待到源碼學習階段咱們會進行更加細緻的分析。

在本篇文章中,咱們將要學習的是容器的另外一個擴展點(FactoryBean),對於FactoryBean官網上的介紹甚短,可是若是咱們對Spring的源碼有必定了解,能夠發現Spring在不少地方都對這種特殊的Bean作了處理。話很少說,咱們開始進入正文。面試

咱們仍是先看看官網上是怎麼說的:緩存

官網介紹

Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

從上面這段文字咱們能夠得出如下幾個信息:mybatis

  1. FactoryBean主要用來定製化Bean的建立邏輯
  2. 當咱們實例化一個Bean的邏輯很複雜的時候,使用FactoryBean是很必要的,這樣能夠規避咱們去使用冗長的XML配置
  3. FactoryBean接口提供瞭如下三個方法:
  • Object getObject(): 返回這個FactoryBean所建立的對象。
  • boolean isSingleton(): 返回FactoryBean所建立的對象是否爲單例,默認返回true。
  • Class getObjectType(): 返回這個FactoryBean所建立的對象的類型,若是咱們能確認返回對象的類型的話,咱們應該正常對這個方法作出實現,而不是返回null。
  1. Spring自身大量使用了FactoryBean這個概念,至少有50個FactoryBean的實現類存在於Spring容器中
  2. 假設咱們定義了一個FactoryBean,名爲myFactoryBean,當咱們調用getBean("myFactoryBean")方法時返回的並非這個FactoryBean,而是這個FactoryBean所建立的Bean,若是咱們想獲取到這個FactoryBean須要在名字前面拼接"&",行如這種形式:getBean("&myFactoryBean")

上面這些概念可能剛剛說的時候你們不是很明白,下面咱們經過FactoryBean的一些應用來進一步體會這個接口的做用。ide

FactoryBean的應用

咱們來看下面這個Demo:源碼分析

public class MyFactoryBean implements FactoryBean {    @Override    public Object getObject() throws Exception {        System.out.println("執行了一段複雜的建立Bean的邏輯");        return new TestBean();    }    @Override    public Class<?> getObjectType() {        return TestBean.class;    }    @Override    public boolean isSingleton() {        return true;    }}public class TestBean {    public TestBean(){        System.out.println("TestBean被建立出來了");    }}// 測試類public class Main {    public static void main(String[] args) {        AnnotationConfigApplicationContext ac=                new AnnotationConfigApplicationContext(Config.class);        System.out.println("直接調用getBean(\"myFactoryBean\")返回:"+ac.getBean("myFactoryBean"));        System.out.println("調用getBean(\"&myFactoryBean\")返回:"+ac.getBean("&myFactoryBean"));    }}

運行後結果以下:學習


執行了一段複雜的建立Bean的邏輯 TestBean被建立出來了 直接調用getBean("myFactoryBean")返回:com.dmz.official.extension.factorybean.TestBean@28f67ac7 調用getBean("&myFactoryBean")返回:com.dmz.official.extension.factorybean.MyFactoryBean@256216b3

咱們雖然沒有直接將TestBean放入Spring容器中,可是經過FactoryBean也完成了這一操做。同時當咱們直接調用getBean("FactoryBean的名稱")獲取到的是FactoryBean建立的Bean,可是添加了「&」後能夠獲取到FactoryBean自己。測試

FactoryBean相關源碼分析

咱們先看下下面這張圖:ui

Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

涉及到FactoryBean主要在3-11-6這一步中,咱們主要關注下面這段代碼:this

// .....省略無關代碼.......// 1.判斷是否是一個FactoryBeanif (isFactoryBean(beanName)) {    // 2.若是是一個FactoryBean那麼在getBean時,添加前綴「&」,獲取這個FactoryBean    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);    if (bean instanceof FactoryBean) {        final FactoryBean<?> factory = (FactoryBean<?>) bean;        boolean isEagerInit;        // 3.作權限校驗,判斷是不是一個SmartFactoryBean,而且不是懶加載的        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)                                                        ((SmartFactoryBean<?>) factory)::isEagerInit,                                                        getAccessControlContext());        }        else {            // 3.判斷是不是一個SmartFactoryBean,而且不是懶加載的            isEagerInit = (factory instanceof SmartFactoryBean &&                           ((SmartFactoryBean<?>) factory).isEagerInit());        }        if (isEagerInit) {            // 4.若是是一個SmartFactoryBean而且不是懶加載的,那麼建立這個FactoryBean建立的Bean            getBean(beanName);        }    }}else {    // 不是一個FactoryBean,直接建立這個Bean    getBean(beanName);}// ...省略無關代碼.....

咱們按照順序一步步分析,首先看第一步:spa

  1. 判斷是否是一個FactoryBean,對應源碼以下:
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {    String beanName = transformedBeanName(name);    // 直接從單例池中獲取這個Bean,而後進行判斷,看是不是一個FactoryBean    Object beanInstance = getSingleton(beanName, false);    if (beanInstance != null) {        return (beanInstance instanceof FactoryBean);    }    // 查找不到這個BeanDefinition,那麼從父容器中再次確認是不是一個FactoryBean    if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {        // No bean definition found in this factory -> delegate to parent.        return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);    }    // 從當前容器中,根據BeanDefinition判斷是不是一個FactoryBean    return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));}
  1. 若是是一個FactoryBean那麼在getBean時,添加前綴「&」,獲取這個FactoryBean
  2. 判斷是不是一個SmartFactoryBean,而且不是懶加載的

這裏涉及到一個概念,就是SmartFactoryBean,實際上這個接口繼承了FactoryBean接口,而且SmartFactoryBean是FactoryBean的惟一子接口,它擴展了FactoryBean多提供了兩個方法以下:

// 是否爲原型,默認不是原型default boolean isPrototype() {    return false;}// 是否爲懶加載,默認爲懶加載default boolean isEagerInit() {    return false;}

從上面的代碼中能夠看出,咱們噹噹實現一個FactoryBean接口,Spring並不會在啓動時就將這個FactoryBean所建立的Bean建立出來,爲了不這種狀況,咱們有兩種辦法:

  • 實現SmartFactoryBean,並重寫isEagerInit方法,將返回值設置爲true
  • 咱們也能夠在一個不是懶加載的Bean中注入這個FactoryBean所建立的Bean,Spring在解決依賴關係也會幫咱們將這個Bean建立出來

實際上咱們能夠發現,當咱們僅僅實現FactoryBean時,其getObject()方法所產生的Bean,咱們能夠當前是懶加載的。

  1. 若是是一個SmartFactoryBean而且不是懶加載的,那麼建立這個FactoryBean建立的Bean。這裏須要注意的是此時建立的不是這個FactoryBean,覺得在getBean時並無加一個前綴「&」,因此獲取到的是其getObject()方法所產生的Bean。

在上面的代碼分析完後,在3-6-11-2中也有兩行FactoryBean相關的代碼,以下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {    // 1.獲取bean名稱    final String beanName = transformedBeanName(name);    Object bean;    //...省略無關代碼...,這裏主要根據beanName建立對應的Bean    // 2.調用getObject對象建立Bean        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);    }
  1. 獲取bean名稱
protected String transformedBeanName(String name) {    // 這個方法主要用來解析別名,若是是別名的話,獲取真實的BeanName    return canonicalName(BeanFactoryUtils.transformedBeanName(name));} // 處理FactoryBeanpublic static String transformedBeanName(String name) {    Assert.notNull(name, "'name' must not be null");    // 沒有帶「&」,直接返回    if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {        return name;    }    // 去除全部的「&」,防止這種寫法getBean("&&&&beanName")    return transformedBeanNameCache.computeIfAbsent(name, beanName -> {        do {            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());        }        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));        return beanName;    });}
  1. 若是是一個FactoryBean,將會調用其getObject()方法,若是不是直接返回。

咱們能夠看到,在調用getObjectForBeanInstance(sharedInstance, name, beanName, null);傳入了一個參數---name,也就是尚未通過transformedBeanName方法處理的bean的名稱,可能會帶有「&」符號,Spring經過這個參數判斷這個Bean是否是一個FactoryBean,若是是的話,會調用其getObject()建立Bean。被建立的Bean不會存放於單例池中,而是放在一個名爲factoryBeanObjectCache的緩存中。具體的代碼由於比較複雜,在這裏咱們就暫且不分析了,你們能夠先留個印象,源碼階段我會作詳細的分析。

Spring中」FactoryBean「概念的彙總(純粹我的觀點)

除了咱們在上文中說到的實現了FactoryBean或者SmartFactoryBean接口的Bean能夠稱之爲一個」FactoryBean「,不知道你們對BeanDefinition中的一個屬性是否還有印象。BeanDefinition有屬性以下(實際上這個屬性存在於AbstractBeanDefinition中):

@Nullableprivate String factoryBeanName;@Nullableprivate String factoryMethodName;

對於這個屬性跟咱們這篇文章中介紹的FactoryBean有什麼關係呢?

首先,咱們看看什麼狀況下bd中會存在這個屬性,主要分爲如下兩種狀況:

第一種狀況:

@Configurationpublic class Config {    @Bean    public B b(){        return new B();    }}

咱們經過@Bean的方式來建立一個Bean,那麼在B的BeanDefinition會記錄factoryBeanName這個屬性,同時還會記錄是這個Bean中的哪一個方法來建立B的。在上面的例子中,factoryBeanName=config,factoryMethodName=b。

第二種狀況:

<bean id="factoryBean" class="com.dmz.official.extension.factorybean.C"/><bean id="b" class="com.dmz.official.extension.factorybean.B" factory-bean="factoryBean" factory-method="b"/>

經過XML的方式進行配置,此時B的BeanDefinition中factoryBeanName=factoryBean,factoryMethodName=b。

上面兩種狀況,BeanDefinition中的factoryBeanName這個屬性均不會爲空,可是請注意此時記錄的這個名字因此對於的Bean並非一個實現了FactoryBean接口的Bean。

綜上,咱們能夠將Spring中的FactoryBean的概念泛化,也就是說全部生產對象的Bean咱們都將其稱爲FactoryBean,那麼能夠總結畫圖以下:

Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

這是我的觀點哈,沒有在官網找到什麼文檔,只是這種比較學習更加能加深印象,因此我把他們作了一個總結,你們面試的時候不用這麼說

Spring官網閱讀系列(七):容器的擴展點(FactoryBean)

跟FactoryBean相關常見的面試題

一、FactoryBean跟BeanFactory的區別

FactoryBean就如咱們標題所說,是Spring提供的一個擴展點,適用於複雜的Bean的建立。mybatis在跟Spring作整合時就用到了這個擴展點。而且FactoryBean所建立的Bean跟普通的Bean不同。咱們能夠說FactoryBean是Spring建立Bean的另一種手段。

而BeanFactory是什麼呢?BeanFactory是Spring IOC容器的頂級接口,其實現類有XMLBeanFactory,DefaultListableBeanFactory以及AnnotationConfigApplicationContext等。BeanFactory爲Spring管理Bean提供了一套通用的規範。接口中提供的一些方法以下:

boolean containsBean(String beanName)Object getBean(String)Object getBean(String, Class)Class getType(String name)boolean isSingleton(String)String[] getAliases(String name)

經過這些方法,能夠方便地獲取bean,對Bean進行操做和判斷。

二、如何把一個對象交給Spring管理

首先,咱們要弄明白一點,這個問題是說,怎麼把一個對象交給Spring管理,「對象」要劃重點,咱們一般採用的註解如@Compent或者XML配置這種相似的操做並不能將一個對象交給Spring管理,而是讓Spring根據咱們的配置信息及類信息建立並管理了這個對象,造成了Spring中一個Bean。把一個對象交給Spring管理主要有兩種方式

  • 就是用咱們這篇文章中的主角,FactoryBean,咱們直接在FactoryBean的getObject方法直接返回須要被管理的對象便可
  • @Bean註解,一樣經過@Bean註解標註的方法直接返回須要被管理的對象便可。

總結

在本文中咱們完成了對FactoryBean的學習,最重要的是咱們須要明白一點,FactoryBean是Spring中特殊的一個Bean,Spring利用它提供了另外一種建立Bean的方式,FactoryBean總體的體系比較複雜,FactoryBean是如何建立一個Bean的一些細節咱們尚未涉及到,不過不要急,在源碼學習階段咱們還會接觸到它,並會對其的整個流程作進一步的分析。目前容器的擴展點咱們還剩最後一個部分,即BeanPostProcessor。BeanPostProcessor貫穿了整個Bean的生命週期,學習的難度更大。但願你們跟我一步步走下去,認認真真學習完Spring,加油!


推薦閱讀

金三銀四季,阿里工做10多年Java大牛的「心得」,獻給迷茫中的你~

相關文章
相關標籤/搜索