為什麼大多數IOC容器使用ApplicationContext,而不用BeanFactory

鍋外的大佬發表於2020-11-13

1. 引言

Spring框架附帶了兩個IOC容器– BeanFactoryApplicationContext. BeanFactory是IOC容器的最基本版本,ApplicationContext擴充套件了BeanFactory的功能。
那麼本篇文章中,我們將通過實際例子瞭解這兩個IOC容器之間的顯著差異。

2. 延遲載入 vs. 預載入

BeanFactory 按需載入bean,而 ApplicationContext 則在啟動時載入所有bean。因此,BeanFactoryApplicationContext相比是輕量級的。讓我們用一個例子來理解它。

2.1. BeanFactory 延遲載入

假設我們有一個名為 Student 單例Bean:

public class Student {
    public static boolean isBeanInstantiated = false;
 
    public void postConstruct() {
        setBeanInstantiated(true);
    }
 
    //standard setters and getters
}

我們將把 postConstruct() 方法定義為BeanFactory配置檔案 ioc-container-difference-example.xml 中的 init method:

<bean id="student" class="com.baeldung.ioccontainer.bean.Student" init-method="postConstruct"/>

現在,讓我們編寫一個測試用例來建立一個BeanFactory 來檢查它是否載入了Student bean:

@Test
public void whenBFInitialized_thenStudentNotInitialized() {
    Resource res = new ClassPathResource("ioc-container-difference-example.xml");
    BeanFactory factory = new XmlBeanFactory(res);
    
    assertFalse(Student.isBeanInstantiated());
}

這裡,沒有初始化 Student 物件。換句話說,只有 BeanFactory 被初始化了。只有當我們顯式呼叫getBean()方法時,BeanFactory 中定義的 bean 才會被載入。
讓我們檢查一下 Student bean 的初始化情況,我們手動呼叫 getBean() 方法:

@Test
public void whenBFInitialized_thenStudentInitialized() {
    Resource res = new ClassPathResource("ioc-container-difference-example.xml");
    BeanFactory factory = new XmlBeanFactory(res);
    Student student = (Student) factory.getBean("student");
 
    assertTrue(Student.isBeanInstantiated());
}

這裡,Student bean 成功載入。因此,BeanFactory 只在需要時載入bean。

2.2. ApplicationContext 預載入

現在,讓我們用ApplicationContext代替BeanFactory
我們只定義ApplicationContext,它將使用預載入策略立即載入所有bean:

@Test
public void whenAppContInitialized_thenStudentInitialized() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ioc-container-difference-example.xml");
    
    assertTrue(Student.isBeanInstantiated());
}

在這裡,即使我們沒有呼叫 getBean() 方法,也會建立 Student 物件
ApplicationContext 被認為是一個沉重的IOC容器,因為它的預載入策略在啟動時載入所有bean。相比之下,BeanFactory 是輕量級的,在記憶體受限的系統中非常方便。儘管如此,大多數用例仍然首選使用 ApplicationContext,這是為什麼呢?

3. 企業應用程式功能

ApplicationContext 以更面向框架的風格增強了BeanFactory,並提供了一些適用於企業應用程式的功能。

例如,它提供了訊息傳遞(i18n或國際化)功能、事件釋出功能、基於註釋的依賴注入,以及與Spring AOP特性的簡單整合。

除此之外,ApplicationContext幾乎支援所有型別的 bean 作用域,但是BeanFactory只支援兩個作用域——SingletonPrototype。因此,在構建複雜的企業應用程式時,最好使用ApplicationContext

4. 自動註冊BeanFactoryPostProcessorBeanPostProcessor

**ApplicationContext 在啟動時自動註冊 BeanFactoryPostProcessor 和 BeanPostProcessor **。然而,BeanFactory不會自動註冊這些介面。

4.1. 在 BeanFactory 中註冊

為了理解,讓我們寫兩個類。
首先,我們有CustomBeanFactoryPostProcessor類,它實現了BeanFactoryPostProcessor

public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    private static boolean isBeanFactoryPostProcessorRegistered = false;
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory){
        setBeanFactoryPostProcessorRegistered(true);
    }
 
    // standard setters and getters
}

這裡,我們重寫了 postProcessBeanFactory() 方法來檢查它的註冊。
其次,我們還有另一個類,CustomBeanPostProcessor,它實現了BeanPostProcessor

public class CustomBeanPostProcessor implements BeanPostProcessor {
    private static boolean isBeanPostProcessorRegistered = false;
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName){
        setBeanPostProcessorRegistered(true);
        return bean;
    }
 
    //standard setters and getters
}

這裡,我們重寫了 PostProcessBeforeAlization() 方法來檢查其註冊。
另外,我們在 ioc-container-difference-example.xml 配置檔案中配置了這兩個類:

<bean id="customBeanPostProcessor" 
  class="com.baeldung.ioccontainer.bean.CustomBeanPostProcessor" />
<bean id="customBeanFactoryPostProcessor" 
  class="com.baeldung.ioccontainer.bean.CustomBeanFactoryPostProcessor" />

讓我們看一個測試用例來檢查這兩個類是否在啟動期間自動註冊:

@Test
public void whenBFInitialized_thenBFPProcessorAndBPProcessorNotRegAutomatically() {
    Resource res = new ClassPathResource("ioc-container-difference-example.xml");
    ConfigurableListableBeanFactory factory = new XmlBeanFactory(res);
 
    assertFalse(CustomBeanFactoryPostProcessor.isBeanFactoryPostProcessorRegistered());
    assertFalse(CustomBeanPostProcessor.isBeanPostProcessorRegistered());
}

從我們的測試中我們可以看到,自動註冊並沒有發生
現在,讓我們看看一個測試用例,手動將它們新增到 BeanFactory:

@Test
public void whenBFPostProcessorAndBPProcessorRegisteredManually_thenReturnTrue() {
    Resource res = new ClassPathResource("ioc-container-difference-example.xml");
    ConfigurableListableBeanFactory factory = new XmlBeanFactory(res);
 
    CustomBeanFactoryPostProcessor beanFactoryPostProcessor 
      = new CustomBeanFactoryPostProcessor();
    beanFactoryPostProcessor.postProcessBeanFactory(factory);
    assertTrue(CustomBeanFactoryPostProcessor.isBeanFactoryPostProcessorRegistered());
 
    CustomBeanPostProcessor beanPostProcessor = new CustomBeanPostProcessor();
    factory.addBeanPostProcessor(beanPostProcessor);
    Student student = (Student) factory.getBean("student");
    assertTrue(CustomBeanPostProcessor.isBeanPostProcessorRegistered());
}

這裡,我們使用 postProcessBeanFactory() 方法註冊 CustomBeanFactoryPostProcessor,使用 addBeanPostProcessor() 方法註冊CustomBeanPostProcessor。在這種情況下,它們都註冊成功。

4.2. 在 ApplicationContext 中註冊

如前所述,ApplicationContext會自動註冊這兩個類,而無需編寫額外的程式碼。
讓我們在單元測試中驗證此行為:

@Test
public void whenAppContInitialized_thenBFPostProcessorAndBPostProcessorRegisteredAutomatically() {
    ApplicationContext context 
      = new ClassPathXmlApplicationContext("ioc-container-difference-example.xml");
 
    assertTrue(CustomBeanFactoryPostProcessor.isBeanFactoryPostProcessorRegistered());
    assertTrue(CustomBeanPostProcessor.isBeanPostProcessorRegistered());
}

我們可以看到,這兩個類的自動註冊都是成功的
因此,建議使用ApplicationContext,因為Spring2.0(及更高版本)大量使用BeanPostProcessor
還有一點值得注意的是如果使用的是普通的 BeanFactory,那麼事務和AOP之類的功能將不會生效(除非你編寫額外的程式碼實現,那就另當別論了)。這樣可能會導致程式碼很混亂,因為配置看起來貌似沒毛病。

5. 寫在結尾

ApplicationContext 提供了一些高階功能,包括一些面向企業應用程式的功能,而BeanFactory只提供了基本功能。因此,一般建議使用 ApplicationContext ,只有在記憶體消耗非常關鍵的情況下,我們才應該考慮去使用BeanFactory。
如果你覺得文章還不錯,記得關注公眾號: 鍋外的大佬
劉一手的部落格

相關文章