Spring官網閱讀(七)容器的擴展點(二)FactoryBean

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

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

  • 一、FactoryBean跟BeanFactory的區別面試

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

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

官網介紹

在這裏插入圖片描述

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

  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的一些應用來進一步體會這個接口的做用。mybatis

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"));
 }
}

運行後結果以下:ide


執行了一段複雜的建立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相關源碼分析

咱們先看下下面這張圖:

在這裏插入圖片描述

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

// .....省略無關代碼.......

// 1.判斷是否是一個FactoryBean
if (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);
}
// ...省略無關代碼.....

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

  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接口,而且SmartFactoryBeanFactoryBean的惟一子接口,它擴展了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> 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));
}

 // 處理FactoryBean
public 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中):

@Nullable
private String factoryBeanName;
@Nullable
private String factoryMethodName;

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

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

第一種狀況:

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

咱們經過@Bean的方式來建立一個Bean,那麼在B的BeanDefinition會記錄factoryBeanName這個屬性,同時還會記錄是這個Bean中的哪一個方法來建立B的。在上面的例子中,factoryBeanName=configfactoryMethodName=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的BeanDefinitionfactoryBeanName=factoryBeanfactoryMethodName=b。

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

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

在這裏插入圖片描述

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

跟FactoryBean相關常見的面試題

一、FactoryBean跟BeanFactory的區別

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

BeanFactory是什麼呢?BeanFactorySpring IOC容器的頂級接口,其實現類有XMLBeanFactoryDefaultListableBeanFactory以及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,咱們直接在 FactoryBeangetObject方法直接返回須要被管理的對象便可
  • @Bean註解,一樣經過 @Bean註解標註的方法直接返回須要被管理的對象便可。

總結

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

您點的每一個贊,我都認真當成了喜歡





本文分享自微信公衆號 - 程序員DMZ(programerDmz)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索