淺析 Spring 的IOC容器

pjmike_pj發表於2018-11-21

原文部落格地址:pjmike的部落格

前言

在前面的文章 淺析Spring 的IoC和DI中簡述了 IOC和DI的基本概念和關係,總體上說,IOC 是一種可以幫助我們解耦各業務物件間依賴關係的物件繫結方式,那麼Spring 提供了兩種容器型別來提供支援 IOC方式。這兩種型別是:

  • BeanFactory: 基礎型別的IOC容器,提供完整的IOC服務支援
  • ApplicationContext: ApplicationContext是在 BeanFactory的基礎之上構建的,是相對高階的容器實現,除了擁有BeanFactory的所有支援,ApplicationContext提供了其他高階特性。

ApplicationContext 和 BeanFactory的繼承關係如下:

applicationContext

可以看到 ApplicationContext 間接繼承自 BeanFactory。

BeanFactory

BeanFactory的介紹

BeanFactory 是基礎型別IoC容器,提供完整的IoC服務支援。如果沒有特殊指定,預設採用延遲初始化策略(lazy-load)只有當客戶端物件需要訪問容器中的某個受管物件的時候,才對該受管物件進行初始化以及依賴注入工作

BeanFactory的物件註冊

BeanFactory,就是生產 Java Bean 的工廠,作為Spring 提供的基本的IoC容器,BeanFactory 幫助完成 業務物件的註冊和物件間依賴關係的繫結

實際上,BeanFactory只是一個介面,它負責定義如何訪問容器內管理的Bean的方法,各個BeanFactory的具體實現類負責具體Bean的註冊以及管理工作。下面是BeanFactory的介面程式碼:

package org.springframework.beans.factory;

public interface BeanFactory {

    /**
     * 用來引用一個例項,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
     */
    String FACTORY_BEAN_PREFIX = "&";

    /*
     * 四個不同形式的getBean方法,獲取例項
     */
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    boolean containsBean(String name); // bean是否存在

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否為單例項

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否為原型(多例項)

    boolean isTypeMatch(String name, Class<?> targetType)
            throws NoSuchBeanDefinitionException;// 名稱、型別是否匹配

    Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 獲取型別

    String[] getAliases(String name);// 根據例項的名字獲取例項的別名

}
複製程式碼

下面我們來測試下一般情況下 BeanFactory介面的具體實現類情況:


// 實體類
@Component
public class Demo {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//Junit測試類
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Autowired
    private BeanFactory beanFactory;
    @Test
    public void test() {
        System.out.println("concrete factory is: " + beanFactory.getClass());
        Assert.assertTrue("Factory can't be null",beanFactory != null);
        Demo demo = (Demo) beanFactory.getBean("demo");
        System.out.println("Found the demo bean: "+demo.getClass());
    }

}
複製程式碼

輸出結果如下:

concrete factory is: class org.springframework.beans.factory.support.DefaultListableBeanFactory
Found the demo bean: class com.pjmike.spring.Demo
複製程式碼

從結果可以看出,具體工廠是 org.springframework.beans.factory.support.DefaultListableBeanFactory的例項。再來看看 BeanFactory的繼承體現:

BeanFactory

從上圖可以看出,BeanFactory有三個直接子類:

  • ListableBeanFactory: 通過繼承該介面可以列出所有的Bean,也可以只列出與預期型別相對應的bean
  • HierarchicalBeanFactory: 支援分層bean的管理,使BeanFactory支援雙親IOC容器的管理功能
  • AutowireCapableBeanFactory: 可以填充不受Spring 控制的 Bean

而三個類的子類體系就更多,詳細的參考 Spring 原始碼。

再來看看之前提到的DefaultListableBeanFactory,它也是上圖中最底層的實現類:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
		...
}
複製程式碼

這個類其實就是 BeanFactory的預設實現類,一個比較通用的BeanFactory實現類,它除了間接實現 BeanFactory介面外,還實現了 BeanDefinitionRegistry介面,該介面才是BeanFactory實現中擔任 Bean註冊管理的角色,它抽象的定義了Bean註冊的邏輯,當然具體的是實現還是靠DefaultListableBeanFactory這等實現類。

ApplicationContext

ApplicationContext的介紹

ApplicationContext是在BeanFactory的基礎上構建的,是相對比較高階的容器實現,除了擁有 BeanFactory的所有支援,ApplicationContext還提供了其他高階特性,比如:

  • 統一資源載入策略
  • 國際化資訊支援
  • 容器內部事件釋出機制

在ApplicationContext 容器啟動之後,預設全部初始化並繫結完成,所以,對於BeanFactory來說,ApplicationContext 往往要求更多的系統資源

ApplicationContext的實現

Spring 中的 Context

Spring 為基本的 BeanFactory 型別容器提供了 XmlBeanFactory 實現(繼承自DefaultListableBeanFactory),相應的,它也為 ApplicationContext 型別容器提供了以下幾個常用的實現:

  • org.springframework.context.support.FileSystemXmlApplicationContext: 在預設情況下,從檔案系統載入 bean 定義以及相關資源的 ApplicationContext 實現
  • org.springframework.context.support.ClassPathXmlApplicationContext: 在預設情況下,從Classpath 載入bean 定義以及相關資源的 ApplicationContext 實現
  • org.springframework.web.context.support.XmlWebApplicationContext: Spring提供的用於 Web 應用程式的 ApplicationContext 實現。

在傳統的基於 XML的Spring專案中,經常會使用到上面的實現類

SpringBoot 中的 Context

在官方文件中給出對於一個 SpringBoot 應用它對應的Context的情況:

context

  • 對於 web應用,context 是 AnnotationConfigServletWebServerApplicationContext
  • 對於 響應式應用,context 是 AnnotationConfigReactiveWebServerApplicationContext
  • 對於普通非 web應用,context 是 AnnotationConfigApplicationContext

以上的 context實際上也是實現了 ApplicationContext介面

ApplicationContext 的簡單實踐

我們都知道IOC容器一般有兩種物件注入方式:基於XML配置檔案 與 基於註解驅動的方式。下面就分別從這兩個角度來看如何使用 ApplicationContext

基於 XML 配置檔案

  1. 定義個實體類
public class User {
    private Integer id;

    private String username;

    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }

    public User() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
複製程式碼
  1. 設定一個XML配置檔案,宣告 User Bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.pjmike.spring.domain.User">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="username" value="pjmike"/>
    </bean>
</beans>
複製程式碼
  1. 主程式
public class XmlBootStrap {
    public static void main(String[] args) {
        //構建一個 ApplicationContext 上下文
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
        //設定此應用上下文的配置路徑
        context.setConfigLocations("classpath:/META-INF/spring/context.xml");
        //呼叫 refresh 方法,完成配置的解析、各種BeanFactoryPostProcessor和BeanPostProcessor的註冊、國際化配置的初始化、web內建容器的構造
        context.refresh();
        User user = context.getBean("user", User.class);
        System.out.print("user.getName() = "+ user.getUsername());
    }
}
複製程式碼

輸出結果

user.getName() = pjmike
複製程式碼

基於註解方式

  1. 宣告一個配置類
@Configuration
public class UserConfiguration {
    @Bean(name = "user")
    public User user() {
        User user = new User();
        user.setUsername("pj");
        return user;
    }
}
複製程式碼
  1. 主程式
public class AnnotationBootStrap {
    public static void main(String[] args) {
        // 構建一個 ApplicationContext 應用上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //註冊一個配置 Bean
        context.register(UserConfiguration.class);
        // 呼叫 refresh 啟動容器
        context.refresh();
        User user = context.getBean("user", User.class);
        System.out.println("user.getName() = "+user.getUsername());
    }
}
複製程式碼

輸出結果

user.getName() = pj
複製程式碼

XML 與 Annotation 簡單對比

從上面的兩個例子可以看出基於XML和基於註解注入Bean 的方式是不一樣的,基於XML的應用上下文ClassPathXmlApplicationContext需要設定配置路徑,基於註解的應用上下文AnnotationConfigApplicationContext需要註冊一個配置Bean,但它們相同的一步就是必須要呼叫 refresh()方法,該方法可以看做是IOC容器的啟動方法,它會做很多操作,比如完成配置的解析、各種BeanFactoryPostProcessor和BeanPostProcessor的註冊、國際化配置的初始化、web內建容器的構造等等,不呼叫它,容器就無法啟動。這裡只是簡要說明,更加詳細的介紹會在後面的文章介紹。

現在是springboot盛行的階段,基於XML配置檔案的方式已經逐步被基於註解的方式所取代,如今的專案中,更多的使用 註解的方式。 關於XML與註解更詳細的對比可以參閱開濤大神的文章: jinnianshilongnian.iteye.com/blog/187991…

小結

上面的文章比較簡單的總結了 BeanFactory 和 ApplicationContext,為後續分析Spring IOC詳細的初始化過程、Spring Bean的載入等做一個鋪墊

參考資料 & 鳴謝

相關文章