Spring 核心概念之一 IoC

2023-06-03 15:00:14

前言

歡迎來到本篇文章!通過上一篇什麼是 Spring?為什麼學它?的學習,我們知道了 Spring 的基本概念,知道什麼是 Spring,以及為什麼學習 Spring。今天,這篇就來說說 Spring 中的核心概念之一 IoC。

IoC 這個概念對於初學者來說還真不是很好理解,我就是那個理解不了的初學者。那時候,學起來很費解,只是迷迷糊糊知道了一些概念名詞,控制反轉,依賴注入。

現在,我重新梳理這些知識,儘量寫清楚什麼是 IoC 以及相關的知識,如有錯誤,敬請指正!好了廢話不多說,進入正題!

什麼是 IoC?什麼是 Spring IoC 容器?

IoC(Inversion of Control),即控制反轉,也被稱為依賴注入(Dependency Injection,DI)

如果有對「依賴」不太明白的朋友,那麼可以去看上一篇,在上一篇中,我們通過 Employee 和 Department 的例子解釋了何為「依賴」

IoC 是一種定義物件之間依賴關係的過程。

在 Spring 沒出現之前,當一個物件需要使用其他物件來完成某些操作,就需要我們自己去建立或查詢這些依賴的物件。

現在,有了 Spring,我們的物件交給 Spring 管理,這些物件可以理解為存放在一個容器中的,這個容器就稱為 Spring IoC容器。在 IoC 容器中,物件不再自己管理它們的依賴,而是通過構造方法引數、工廠方法的引數或者在物件建立後通過屬性設定來定義它們的依賴關係

Spring 的 IoC 容器負責在建立物件時注入它們依賴的其他物件,也就是自動地把依賴的物件提供給需要它們的物件。這樣一來,物件不再需要主動去查詢或建立它們的依賴,而是由容器在建立物件時幫助它們完成依賴注入的過程。

控制反轉的概念主要是與傳統的直接構造(即 new 操作)來控制物件依賴的方式相反。傳統方式中,一個物件通常會直接建立或查詢它所依賴的其他物件,而在 IoC 中,物件將自身的控制權交給了容器,容器負責管理物件的建立和依賴注入,因此被稱為「控制反轉」。

初次見面 BeanFactory 和 ApplicationContext

在 Spring Framework 中,org.springframework.beansorg.springframework.context 包是 Spring IoC 容器的基礎。

下面介紹兩個新手村的夥伴給大家認識,BeanFactory 介面和它的子介面 ApplicationContext 介面。

BeanFactory 介面提供了一個高階設定機制,能夠管理任何型別的物件。

對於它的子介面 ApplicationContext 來說,它的子介面增加了以下功能:

  • 更容易與 Spring AOP 特性整合

  • 訊息資源處理(用於國際化)

  • 事件釋出

  • 應用程式層特定上下文,例如 WebApplicationContext,用於 Web 應用程式。

簡而言之,BeanFactory 提供設定框架和基本功能,而 ApplicationContext 新增了更多企業特定功能。

什麼是 Bean?

在 Spring 中,構成應用程式骨幹並由 Spring IoC 容器管理的物件稱為 Bean。 Bean 是由 Spring IoC 容器範例化、組裝和管理的物件。否則,Bean 只是我們應用程式中眾多物件中的一個普通的物件而已。

Bean 及其相互依賴關係是反映在容器使用的設定後設資料(Configuration Metadata)中的,這個設定後設資料可以用 XML、Java 註解或 Java 程式碼錶示。

提示:如果你和我一樣比較喜歡深究這些英文單詞的中文意思,現在的我給你個建議:

就是覺得沒必要深究

比如說 Bean,中文是什麼意思,你去找翻譯,發現翻譯是「豆」,還有類似 JavaBean,翻譯是「Java 豆」,這些都是毫無意義的翻譯,所以沒必要知道它的中文意思是什麼,Bean 就是 Bean,Bean 就是被 Spring IoC 容器管理的物件,這就是 Bean;JavaBean 則是隻提供 setter 和 getter 的純物件,本身沒有任何業務邏輯,這就是 JavaBean。

容器是誰?

我們一直談到「容器」,那麼容器到底是什麼呢?嘿嘿,容器馬上就揭曉了!

實際上,Spring 的 IoC 容器就是由 org.springframework.context.ApplicationContext 介面來代表的。這個容器承擔著範例化、設定和組裝Bean的責任。

容器通過讀取設定後設資料來了解如何建立、設定和組裝物件,同時也允許我們描述應用程式中各個物件之間的複雜依賴關係。目前從本系列的角度來看,我們會使用傳統的 XML 方式來定義設定後設資料,這是我們需要學習和了解的,後續才能更好地理解使用 Java 註解或程式碼作為設定後設資料的方式。

Spring 為我們提供了多個 ApplicationContext 介面的實現。在獨立的應用程式中,常見的範例化方式是建立 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的一個範例。

不過,在我們日常的開發和工作中,我們基本上不需要顯式地去範例化一個或多個 Spring 容器。特別是在Web應用程式的場景下,通常只需在web.xml檔案中簡單地編寫約8行標準的XML設定即可完成(你可以參考一些方便的方式來初始化Web應用程式的ApplicationContext)。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

所以,容器終於來啦!它就是 Spring 的 IoC 容器,負責管理 Bean 的範例化、設定和組裝,而我們可以通過設定後設資料來描述應用程式中的物件和它們之間的依賴關係。記住,我們一般不需要手動範例化 Spring 容器,特別是在 Web 應用程式中。

Spring IoC 容器怎樣執行的?

從頂層上來看,Spring IoC 是這樣執行的,就是將我們應用程式中的各種業務物件與設定後設資料結合起來,使得我們在初始化 ApplicationContext 之後,有一個完整設定的、可用的應用程式。

什麼是設定後設資料?

先說個結論,目前日常工作中,設定後設資料基本都是以 Java 註解或者 Java 程式碼的方式來提供給 Spring 容器的,不過 XML 的方式我們也要學習,對於後續學習是有幫助的。

「設定後設資料」是 Configuration Metadata,不是 Configure Metadata,這裡的「設定」二字是名詞,不是動詞,千萬不要理解成去設定後設資料。

這個設定後設資料,實際就是用來描述設定的資料,上面我也說了,我們可以用 Java 註解或者 Java 程式碼的方式來描述設定,也可以用 XML 的方式來描述資料。

所以現在,相信你已經明白何為設定後設資料了,所以學習下以 XML 格式的檔案作為設定後設資料。

XML 格式的設定後設資料

設定後設資料以簡單直觀的 XML 格式提供,這也是本系列前大半部分內容用來傳達 Spring IoC 容器關鍵概念和特性的方式,也正如前面說的,學習 XML 的設定方式,便於我們後續學習 Java 註解或者 Java 程式碼的設定方式。

下面的範例展示了基於XML的設定後設資料的基本結構

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">  
        <!-- 該 bean 的共同作業者和設定寫在此處 -->
    </bean>

    <bean id="..." class="...">
        <!-- 該 bean 的共同作業者和設定寫在此處 -->
    </bean>

    <!-- 更多的 bean 定義寫在此處 -->

</beans>

上面的範例中,主要使用了 <bean> 元素(或者說標籤),該元素有兩個屬性,idclass

  • id 屬性:它是一個字串,用於標識單個 bean 定義。

  • class 屬性:它定義了 bean 的型別,並使用完全限定類名(又稱全限定名、全類名。多種叫法,都是同一個東西)。

我們習慣說把物件交給 Spring IoC 容器管理,那你如何個交法呢

上面的 XML 已經給出了答案,就是定義 Bean,我們每定義一個 Bean,就是將對應的類的物件交給了 Spring IoC 容器了

這些 Bean 的定義就是構成我們應用程式中的各種實際物件。一般我們在開發的時候,都會分層次的,控制層、業務層、持久層、表現層(檢視層)或者其他層次,然後我們就會定義業務層物件、持久層物件、表現層物件等等。

在上一篇中,我舉了個例子,員工和部門的,讓這兩個東西交給了 Spring IoC 管理了,實際上,在日常開發中,是不會這樣做的,不會設定細粒度的領域物件(Domain Object)。因為一般這些領域物件都是在業務層和持久層中建立或者載入的。

如何範例化一個 Spring IoC 容器?

上面我也說過,在我們日常的開發和工作中,我們基本上不需要顯式地去範例化一個或多個 Spring 容器的。

但是我們現在在學習,就有必要了解如何手動去範例化一個 Spring IoC 容器。

一行程式碼就能夠範例化一個 Spring IoC 容器:

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

在 XML 為設定後設資料的情況下,我們可以建立一個 ClassPathXmlApplicationContext 物件,它是 ApplicationContext 的一個實現類,提供給我們的建構函式的引數是一條或多條路徑是資源字串,它讓容器從各種外部資源(如本地檔案系統、Java CLASSPATH 等)載入設定後設資料,這樣我們就範例化一個 Spring IoC 容器,context 物件就是這個容器了。

現在我們將持久層的物件和業務層的物件定義到 XML 中,先建立好需要的類和介面:

接著,在 daos.xml 中定義如下兩個 bean,交給 Spring IoC 管理:

<?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="roleDao" class="cn.god23bin.demo.dao.impl.RoleDaoImpl">
        <!-- 該 bean 的共同作業者和其他設定 -->
    </bean>

    <bean id="userDao" class="cn.god23bin.demo.dao.impl.UserDaoImpl">
        <!-- 該 bean 的共同作業者和其他設定 -->
    </bean>

    <!-- 其他的持久層的 bean 定義在這裡 -->

</beans>

services.xml 同理:

<?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">

    <!-- 引入另一個 XML 定義的 bean -->
    <import resource="daos.xml"/>

    <bean id="userService" class="cn.god23bin.demo.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao" />
        <property name="roleDao" ref="roleDao" />
    </bean>

    <!-- 其他的業務層的 bean 定義在這裡 -->

</beans>

services.xml 中,使用 <import /> 可以讓 Bean 的定義跨越 XML 檔案。一般每個單獨的 XML 組態檔代表了我們應用中的一個邏輯層或者模組,就如同這裡的 daos.xmlservices.xml

使用 Spring IoC 容器

我們把物件交給了 Spring IoC 容器管理,讓它幫我們建立物件以及處理物件之間的依賴關係。

在上面的 XML 中,我們定義了 UserServcie 物件,即把 UserService 這個物件交給了 Spring IoC,那麼如何從容器獲取它呢?

ApplicationContext 是一個高階工廠的介面,能夠維護不同 Bean 及其依賴關係的登入檔。我們通過使用方法 T getBean(String name, Class requiredType),就可以獲取到我們需要的 Bean 物件。

程式碼如下:

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
UserService userService = context.getBean("userService", UserService.class);
String roleList = userService.getRoleList();
String username1 = userService.getUsernameById(23L);
String username2 = userService.getUsernameById(24L);
System.out.println("roleList = " + roleList + " | username1 = " + username1 + " | username2 = " + username2);

輸出:

總結

以上,就是本文的所有內容,主要講了什麼是 Spring IoC 容器,介紹了 BeanFactoryApplicationContext

實際上這個 ApplicationContext 就代表容器,它會讀取我們的設定後設資料,這樣它就知道該去管理哪些物件了。

也介紹了什麼是 Bean,實際上就是被容器管理的物件,都是所謂的 Bean,也習慣稱為 Bean 物件。

還介紹了 Spring IoC 容器從頂層上來看是怎樣執行的,就是將各種業務物件和設定後設資料相結合,組成一個完整設定的、可用的應用程式。

對於設定後設資料,這個可以有多種形式,可以是 XML,可以是 Java 註解,可以是 Java 程式碼。

最後就介紹瞭如何去範例化並使用 Spring IoC 容器,雖然我們日常開始是不會這樣去做的,不會去建立一個容器,然後通過容器的 getBean 去獲取 Bean 進行操作,但是我們就是需要了解學習,因為這些就是 Spring 的基礎。

最後的最後

希望各位螢幕前的靚仔靚女們給個三連!你輕輕地點了個贊,那將在我的心裡世界增添一顆明亮而耀眼的星!

咱們下期再見!