天天看點

Spring核心FactoryBean,BeanFactory,ApplicationContext的差別

作者:Java小陳

1 FactoryBean和BeanFactory差別

在 Spring 中,有兩個接口:BeanFactory 和 FactoryBean 因為名字相近很容易被混淆。那他們之間有什麼差別呢

1.1 BeanFactory

1.1.1 定義

BeanFactory,以Factory結尾,表示它是一個工廠類(接口), 它負責生産和管理bean的一個工廠

BeanFactory定義了IOC容器的最基本形式,并提供了IOC容器應遵守的的最基本的接口,也就是Spring IOC所遵守的最底層和最基本的程式設計規範。

在Spring中,BeanFactory是IOC容器的核心接口,它的職責包括:執行個體化、定位、配置應用程式中的對象及建立這些對象間的依賴

在Spring代碼中,BeanFactory隻是個接口,并不是IOC容器的具體實作,但是Spring容器給出了很多種實作,比如:

  • DefaultListableBeanFactory。
  • XmlBeanFactory
  • ApplicationContext

這些實作類從不同的次元對 beanFactory 進行了擴充。

1.1.2 beanFactory 源碼

package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

public interface BeanFactory {

    factoryBean 的轉義辨別符。(具體用法後面有介紹)
    String FACTORY_BEAN_PREFIX = "&";
        
    根據 name 從容器中拿對應的 bean
    Object getBean(String name) throws BeansException;
    
    根據 name 和 type 從容器中拿對應的 bean,要對 bean 的類型做校驗。
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    
    在容器中能否找到與 name 比對的 bean 或者 beanDefinition。
    boolean containsBean(String name);
    
    判斷 name 對對應的 bean 是不是 單例。
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    判斷 name 對應的 bean 與指定的類型是否比對。
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    
    boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    
    根據 name 擷取對應的 bean 的類型。
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    
    根據 name 擷取對應 bean 的 别名。
    String[] getAliases(String name);
}
           

1.1.3 BeanFactory體系結構

BeanFactory 的類體系結構

  • BeanFactory: 接口位于類結構樹的頂端, 它最主要的方法就是getBean(StringbeanName),該方法從容器中傳回特定名稱的Bean,BeanFactory 的功能通過其他的接口得到不斷擴充。
  • ListableBeanFactory:該接口定義了通路容器中Bean 基本資訊的若幹方法,如檢視Bean 的個數、擷取某一類型Bean 的配置名、檢視容器中是否包括某一Bean 等方法;
  • HierarchicalBeanFactory:父子級聯IoC 容器的接口,子容器可以通過接口方法通路父容器;
  • ConfigurableBeanFactory:是一個重要的接口,增強了IoC 容器的可定制性,它定義了設定類裝載器、屬性編輯器、容器初始化後置處理器等方法;
  • AutowireCapableBeanFactory:定義了将容器中的Bean 按某種規則(如按名字比對、按類型比對等)進行自動裝配的方法;
  • SingletonBeanRegistry:定義了允許在運作期間向容器注冊單執行個體Bean 的方法;
  • BeanDefinitionRegistry:Spring 配置檔案中每一個<bean>節點元素在Spring 容器裡都通過一個BeanDefinition對象表示,它描述了Bean的配置資訊。而BeanDefinitionRegistry 接口提供了向容器手工注冊BeanDefinition 對象的方法

1.1.4 使用場景

使用場景:

  • 通過 名字或類型從容器中擷取 bean。
  • 判斷容器中是否包含指定的 bean。
  • 判斷 bean 是不是單例。

1.2 FactoryBean

一般情況下,Spring通過反射機制利用<bean>的class屬性指定實作類執行個體化Bean,但在某些情況下,執行個體化Bean過程比較複雜,如果按照傳統的方式,則需要在<bean>中提供大量的配置資訊。配置方式的靈活性是受限的,這時采用編碼的方式可能會得到一個簡單的方案。Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類接口,使用者可以通過實作該接口定制執行個體化Bean的邏輯。

首先 FactoryBean 是一個 bean,但它又不僅僅是個 bean。它是一個可以 建立 或 修飾 其他對象的工廠 bean,這跟設計模式中的工廠模式或者裝飾模式很相似,它可以建立除自身以外的其他對象

1.2.1 FactoryBean源碼

public interface FactoryBean<T> {   
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";   
    
    從 factory 中擷取 bean
    @Nullable
    T getObject() throws Exception;
    從 beanFactory 中擷取類型
    
    @Nullable
    Class<?> getObjectType();
    
    判斷是單例?
    default boolean isSingleton() {
        return true;
    }    
}
           

從上面的接口可以看出,FactoryBean 有工廠的味道。也就是說,如果一個 A類實作了 FactoryBean 接口,那麼 類A 就變成了 A工廠。根據 A 的名稱獲得的實際上是工廠調用 getObject() 傳回的對象, 而不是 A工廠自己。如果想獲得 A工廠自己的執行個體,需要添加 & 字首。

1.2.2 用個demo解釋下

這是一個簡單的 FactoryBean 的使用

package com.example.demo.domian;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class MyFactoryBean implements FactoryBean { 
  // 儲存一句話,用來區分不同的對象。  
  private String message;
  // 無參構造器。
  public MyFactoryBean() {
      // 意思是:目前對象是 MyFactoryBean 的對象。
    this.message = "object of myFactoryBeanSelf";
  }
  // 有參構造器。
  public MyFactoryBean(String message) {
    this.message = message;
  }
  // 擷取 message。
  public String getMessage() {
    return this.message;
  }

  @Override
  /**
   *  這個方法在執行時建立了新的 MyFactoryBean 類型的對象。
   *  這裡繼續沿用了 MyFactoryBean 類型,但是可以是别的類型
   *  比如:Person、Car、等等。
   */
  public Object getObject() throws Exception {
      // 意思是:目前對象是 MyFactoryBean 的 getObject() 建立的。
    return new MyFactoryBean("object from getObject() of MyFactoryBean");
  }

  @Override
  public Class<?> getObjectType() {
    return MyFactoryBean.class
  }
}
           

測試内容很簡單,MyFactoryBean 被 @Component 注解了,當啟動SpringBoot程式的時候,會為 MyFactoryBean 建構 bean 并且儲存到容器中。我們要測試的就是用 類: MyFactoryBean 的 名字: myFactoryBean 去容器中拿 bean,看拿到的結果是什麼?

package com.example.demo.domian;


import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = com.example.demo.domian.MyFactoryBean.class)
class MyFactoryBeanTest {
  @Autowired
  private ApplicationContext context;

  @Test
  public void test() {
    // 第一次用 myFactoryBean 去拿。  
    MyFactoryBean myBean1 = (MyFactoryBean) context.getBean("myFactoryBean");
    System.out.println("myBean1 = " + myBean1.getMessage());
    // 第二次用 &myFactoryBean 去拿。  
    MyFactoryBean myBean2 = (MyFactoryBean) context.getBean("&myFactoryBean");
    System.out.println("myBean2 = " + myBean2.getMessage());、
    // 判斷兩次拿到的對象是不是一樣的?    
    System.out.println("myBean1.equals(myBean2) = " + myBean1.equals(myBean2));
  }
}
測試結果:
myBean1 = object from getObject() of MyFactoryBean
myBean2 = object of myFactoryBeanSelf
myBean1.equals(myBean2) = false
           

結果很明顯:

第一次使用 myFactoryBean 去容器中拿,實際上是容器中 MyFactorybean的bean 調用了 getObject()方法,并将結果傳回。

第二次使用 &myFactoryBean 去容器中拿,才是真正拿到了 MyFactorybean 的 bean。

兩次拿出來的對象當然是不一樣的。

而 & 就是用于區分到底拿誰的的字首。

1.2.3 使用場景

為什麼需要 FactoryBean ,它的特殊功能是什麼呢?

Spring 中FactoryBean最大的應用場景是用在 AOP 中。我們都知道,AOP 實際上是 Spring 在運作是建立出來的代理對象,這個對象是在運作時才被建立的,而不是在啟動時定義的,這與工廠方法模式是一緻的。更生動地說,AOP 代理對象通過 java 反射機制在運作時建立代理對象,并根據業務需求将相應的方法編織到代理對象的目标方法中。Spring 中的 ProxyFactoryBean 就是幹這事的。

是以,FactoryBean 提供了更靈活的執行個體化 bean 的方法。通過 FactoryBean 我們可以建立更複雜的 bean

1.3 差別總結

  • BeanFactory:IOC 容器,并且提供方法支援外部程式對這些 bean 的通路,在程式啟動時 根據傳入的參數産生各種類型的 bean,并添加到 IOC容器(實作 BeanFactory 接口的類) 的 singletonObject 屬性中。
  • FactoryBean: 首先是個 bean,也存放在 BeanFactory 中。它具有工廠方法的功能,在程式運作中 産生指定(一種)類型的 bean,并添加到了 IOC容器中的 factoryBeanObjectCache 屬性中。

    是以,這兩種方式建立的 bean 都是被 spring 容器管理的

  • BeanFactory和FactoryBean都可以用來建立對象,隻不過建立的流程和方式不同 當使用BeanFactory的時候,必須要嚴格的遵守bean的生命周期,經過一系列繁雜的步驟之後可以建立出單例對象,是流水線式的建立過程 FactoryBean是使用者可以自定義bean對象的建立流程,不需要按照bean的生命周期來建立

轉載于:https://blog.csdn.net/yy_diego/article/details/115710104

2 BeanFactory和ApplicationContext差別

BeanFactory已經在上面說過了,下面就不再贅述了

2.1 ApplicationContext

2.1.1 和BeanFactory關系

BeanFactory和ApplicationContext 接口及其子類圖

Spring核心FactoryBean,BeanFactory,ApplicationContext的差別

2.1.2 定義

繼承了BeanFactory接口,擁有BeanFactory的全部功能,并且擴充了很多進階特性,每次容器啟動時就會建立所有的對象。

建立ApplicationContext的方法:

  • 從類路徑下加載配置檔案:ClassPathXmlApplicationContext("applicationContext.xml");
  • 從硬碟絕對路徑下加載配置檔案: FileSystemXmlApplicationContext(“d:/xxx/yyy/xxx”);

2.1.3 ApplicationContext類體系結構

Bean 工廠(com.springframework.beans.factory.BeanFactory)是Spring架構最核心的接口,它提供了進階IOC的配置機制。

應用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory 基礎之上。

幾乎所有的應用場合我們都直接使用ApplicationContext 而非底層的BeanFactory

ApplicationContext 由BeanFactory 派生而來,提供了更多面向實際應用的功能。在BeanFactory中,很多功能需要以程式設計的方式實作,而在ApplicationContext 中則可以通過配置的方式實作。

ApplicationContext 的主要實作類是ClassPathXmlApplicationContext 和FileSystemXmlApplicationContext,前者預設從類路徑加載配置檔案,後者預設從檔案系統中裝載配置檔案。

核心接口包括:

  • ApplicationEventPublisher:讓容器擁有釋出應用上下文事件的功能,包括容器啟動事件、關閉事件等。實作了ApplicationListener事件監聽接口的Bean 可以接收到容器事件, 并對事件進行響應處理。在ApplicationContext抽象實作類AbstractApplicationContext中,我們可以發現存在一個ApplicationEventMulticaster,它負責儲存所有監聽器,以便在容器産生上下文事件時通知這些事件監聽者
  • MessageSource:為應用提供i18n國際化消息通路的功能;
  • ResourcePatternResolver : 所有ApplicationContext實作類都實作了類似于PathMatchingResourcePatternResolver的功能,可以通過帶字首的Ant 風格的資源檔案路徑裝載Spring 的配置檔案。
  • LifeCycle:該接口是Spring 2.0 加入的,該接口提供了start()和stop()兩個方法,主要用于控制異步處理過程。在具體使用時,該接口同時被 ApplicationContext實作及具體Bean實作,ApplicationContext會将start/stop的資訊傳遞給容器中所有實作了該接口的Bean,以達到管理和控制JMX、任務排程等目的。
  • ConfigurableApplicationContext 擴充于ApplicationContext,它新增加了兩個主要的方法:refresh()和close(),讓ApplicationContext具有啟動、重新整理和關閉應用上下文的能力。在應用上下文關閉的情況下調用refresh()即可啟動應用上下文,在已經啟動的狀态下,調用refresh()則清除緩存并重新裝載配置資訊,而調用close()則可關閉應用上下文。這些接口方法為容器的控制管理帶來了便利.

代碼示例:

ApplicationContext ctx =new ClassPathXmlApplicationContext("com/baobaotao/context/beans.xml");
ApplicationContext ctx =new FileSystemXmlApplicationContext("com/baobaotao/context/beans.xml");
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});           

2.2 結論

早期的電腦性能低,記憶體小,是以Spring容器的容量不足,不能将所有的對象全部建立好放入容器,是以使用的是BeanFactory,需要某個對象時,再進行建立,随着電腦硬體的發展,記憶體越來越大,是以Spring架構引入了ApplicationContext,将所有的對象都建立好,放入容器,使用哪個對象,從容器中取得即可。

是以,web開發中,使用applicationContext. 在資源匮乏的環境可以使用BeanFactory

是以,ApplicationContext 的初始化和BeanFactory 有一個重大的差別:BeanFactory在初始化容器時,并未執行個體化Bean,直到第一次通路某個Bean 時才執行個體目标Bean;而ApplicationContext則在初始化應用上下文時就執行個體化所有單執行個體的Bean

ApplicationContext接口,它由BeanFactory接口派生而來,ApplicationContext包含BeanFactory的所有功能,通常建議比BeanFactory優先