1. 程式人生 > 實用技巧 >Spring IOC詳解 快速入門

Spring IOC詳解 快速入門

Spring 與 IoC

IoC (IOC,Inversion of Control)是一個概念,是一種思想,其實現方式多種多樣。當前比較流行的實現方式有兩種:依賴注入和依賴查詢。依賴注入方式應用更為廣泛。
依賴查詢:Dependency Lookup,DL,容器提供回撥介面和上下文環境給元件,程式程式碼則需要提供具體的查詢方式。比較典型的是依賴於 JNDI 系統的查詢。
依賴注入:Dependency Injection,DI,程式程式碼不做定位查詢,這些工作由容器自行完成。
依賴注入 DI 是指程式執行過程中,若需要呼叫另一個物件協助時,無須在程式碼中建立被呼叫者,而是依賴於外部容器,由外部容器建立後傳遞給程式。

Spring 的依賴注入對呼叫者與被呼叫者幾乎沒有任何要求,完全支援 POJO 之間依賴關係的管理。
依賴注入是目前最優秀的解耦方式。依賴注入讓 Spring 的 Bean 之間以配置檔案的方式組織在一起,而不是以硬編碼的方式耦合在一起的。

spring程式開發

在普通三層架構的基礎上,將程式修改為 Spring 框架程式
舉例:springDemo

匯入 Jar 包

首先,匯入 Spring 程式開發的四個基本 jar 包。
在這裡插入圖片描述
其次,匯入日誌相關的 Jar 包。
在 依 賴 庫 spring-framework-3.0.2.RELEASE-dependencies.zip 解 壓 目 錄 下 :\org.apache.commons\com.springsource.org.apache.commons.logging\1.1.1 下 的com.springsource.org.apache.commons.logging-1.1.1.jar 檔案。該檔案只是日誌記錄的實現規範,並沒有具體的實現。相當於 slf4j.jar 的作用。

這裡日誌的實現使用 log4j,故還需要 log4j.jar。在依賴庫解壓目錄下:\org.apache.log4j\com.springsource.org.apache.log4j\1.2.15 中的 com.springsource.org.apache.log4j-1.2.15.jar最後,匯入 JUnit 測試 Jar 包 junit-4.9.jar。
Spring 基本程式設計,共需 7 個 Jar 包即可。
在這裡插入圖片描述

定義介面與實體類

在這裡插入圖片描述
在這裡插入圖片描述

建立 Spring 配置檔案

Spring 配置檔案的檔名可以隨意,但 Spring 建議的名稱為 applicationContext.xml。檔案約束在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 檔案中。

在這裡插入圖片描述
在這裡插入圖片描述
注意,Spring 配置檔案中使用的約束檔案為 xsd 檔案。若 Eclipse 中沒有自動提示功能,則需要將約束要查詢的域名地址指向本地的 xsd 檔案。相應的 xsd 檔案在 Spring 框架解壓目錄下的 schema 目錄的相應子目錄中。
這裡需要的是 spring-beans.xsd 約束檔案,故需要在 beans 子目錄中查詢相應版本的約束檔案。
在這裡插入圖片描述
在這裡插入圖片描述
< bean />:用於定義一個例項物件。一個例項對應一個 bean 元素。
id:該屬性是 Bean 例項的唯一標識,程式通過 id 屬性訪問 Bean,Bean 與 Bean 間的依賴關係也是通過 id 屬性關聯的。
class:指定該 Bean 所屬的類,注意這裡只能是類,不能是介面。

定義測試類

在這裡插入圖片描述
(1)ApplicationContext 介面容器
ApplicationContext 用於載入 Spring 的配置檔案,在程式中充當“容器”的角色。其實現
類有兩個。通過 Ctrl +T 檢視:
在這裡插入圖片描述
A、配置檔案在類路徑下
若 Spring 配置檔案存放在專案的類路徑下,則使用ClassPathXmlApplicationContext 實現類進行載入。
在這裡插入圖片描述
B、配置檔案在本地目錄中
若 Spring 配置檔案存放在本地磁碟目錄中,則使用 FileSystemXmlApplicationContext 實現類進行載入。
在這裡插入圖片描述
C、配置檔案在專案根路徑下
若 Spring 配置檔案存放在專案的根路徑下,同樣使用 FileSystemXmlApplicationContext實現類進行載入。下面是存放在專案根路徑下的情況,該配置檔案與 src 目錄同級,而非在 src 中。
在這裡插入圖片描述
在這裡插入圖片描述(2)BeanFactory 介面容器
BeanFactory 介面物件也可作為 Spring 容器出現。BeanFactory 介面是 ApplicationContext介面的父類。
在這裡插入圖片描述
若要建立 BeanFactory 容器,需要使用其實現類 XmlBeanFactory(Ctrl+T 檢視繼承關係)。該類可以載入 Spring 配置檔案。
在這裡插入圖片描述
而 Spring 配置檔案以資源 Resouce 的形式出現在 XmlBeanFactory 類的構造器引數中。Resouce 是一個介面,其具有兩個實現類:
ClassPathResource:指定類路徑下的資原始檔
FileSystemResource:指定專案根路徑或本地磁碟路徑下的資原始檔。
在這裡插入圖片描述
在建立了 BeanFactory 容器後,便可使用其過載的 getBean()方法,從容器中獲取指定的Bean 物件。
在這裡插入圖片描述
在這裡插入圖片描述
(3)兩個介面容器的區別
雖然這兩個介面容器所要載入的 Spring 配置檔案是同一個檔案,但在程式碼中的這兩個容器物件卻不是同一個物件,即不是同一個容器:它們對於容器內物件的裝配(建立)時機是不同的。
裝配時機測試時需要注意,首先要在容器中物件StudentServiceImpl 類的無參構造器中新增一個輸出語句,以顯示其是否執行。
在這裡插入圖片描述
A、ApplicationContext 容器中物件的裝配時機
ApplicationContext 容器,會在容器物件初始化時,將其中的所有物件一次性全部裝配好。
以後程式碼中若要使用到這些物件,只需從記憶體中直接獲取即可。執行效率較高。但佔用記憶體。
在這裡插入圖片描述
B、BeanFactory 容器中物件的裝配時機
BeanFactory 容器,對容器中物件的裝配與載入採用延遲載入策略,即在第一次呼叫getBean()時,才真正裝配該物件。
在這裡插入圖片描述

Bean的裝配

舉例:beanAssemble 專案Bean 的裝配,即 Bean 物件的建立。容器根據程式碼要求建立 Bean 物件後再傳遞給程式碼的過程,稱為 Bean 的裝配。

預設裝配方式

程式碼通過 getBean()方式從容器獲取指定的 Bean 例項,容器首先會呼叫 Bean 類的無參構造器,建立空值的例項物件。
舉例:ba01 包
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

動態工廠 Bean

有些時候,專案中需要通過工廠類來建立 Bean 例項,而不能像前面例子中似的,直接由 Spring 容器來裝配 Bean 例項。使用工廠模式建立 Bean 例項,就會使工廠類與要建立的Bean 類耦合到一起。
(1)將動態工廠 Bean 作為普通 Bean 使用
將動態工廠 Bean 作為普通 Bean 來使用是指,在配置檔案中註冊過動態工廠 Bean 後,測試類直接通過 getBean()獲取到工廠物件,再由工廠物件呼叫其相應方法建立相應的目標物件。配置檔案中無需註冊目標物件的 Bean。因為目標物件的建立不由 Spring 容器來管理。
舉例:ba02 包
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
但,這樣做的缺點是,不僅工廠類與目標類耦合到了一起,測試類與工廠類也耦合到了一起。
(2)使用 Spring 的動態工廠 Bean
Spring 對於使用動態工廠來建立的 Bean,有專門的屬性定義。factory-bean 指定相應的工廠 Bean,由 factory-method 指定建立所用方法。此時配置檔案中至少會有兩個 Bean 的定義:工廠類的 Bean,與工廠類所要建立的目標類 Bean。而測試類中不再需要獲取工廠 Bean物件了,可以直接獲取目標 Bean 物件。實現測試類與工廠類間的解耦。
在這裡插入圖片描述
在這裡插入圖片描述

靜態工廠 Bean

使用工廠模式中的靜態工廠來建立例項 Bean。
此時需要注意,靜態工廠無需工廠例項,所以不再需要定義靜態工廠。
而對於工廠所要建立的 Bean,其不是由自己的類建立的,所以無需指定自己的類。但其是由工廠類建立的,所以需要指定所用工廠類。故 class 屬性指定的是工廠類而非自己的類。當然,還需要通過 factory-method 屬性指定工廠方法。
舉例:ba03 包
在這裡插入圖片描述
在這裡插入圖片描述
容器中 Bean 的作用域
當通過 Spring 容器建立一個 Bean 例項時,不僅可以完成 Bean 的例項化,還可以通過scope 屬性,為 Bean 指定特定的作用域。Spring 支援 5 種作用域。
(1)singleton:單態模式。即在整個 Spring 容器中,使用 singleton 定義的 Bean 將是單例的,只有一個例項。預設為單態的。
(2)prototype:原型模式。即每次使用 getBean 方法獲取的同一個的例項都是一個新的例項。
(3)request:對於每次 HTTP 請求,都將會產生一個不同的 Bean 例項。
(4)session:對於每個不同的 HTTP session,都將產生一個不同的 Bean 例項。
(5)global session:每個全域性的 HTTP session 對應一個 Bean 例項。典型情況下,僅在使用portlet 叢集時有效,多個 Web 應用共享一個 session。一般應用中,global-session 與 session是等同的。
注意:
(1)對於 scope 的值 request、session 與 global session,只有在 Web 應用中使用 Spring 時,該作用域才有效。
(2)對於 scope 為 singleton 的單例模式,該 Bean 是在容器被建立時即被裝配好了。 (3)對於 scope 為 prototype 的原型模式,Bean 例項是在程式碼中使用該 Bean 例項時才進行裝配的。
舉例:ba04 包
在這裡插入圖片描述
在這裡插入圖片描述

Bean 後處理器

Bean 後處理器是一種特殊的 Bean,容器中所有的 Bean 在初始化時,均會自動執行該類的兩個方法。由於該 Bean 是由其它 Bean 自動呼叫執行,不是程式設計師手工呼叫,故此 Bean無須 id 屬性。
需要做的是,在 Bean 後處理器類方法中,只要對 Bean 類與 Bean 類中的方法進行判斷,就可實現對指定的 Bean 的指定方法進行功能擴充套件與增強。方法返回的 Bean 物件,即是增過的物件。
程式碼中需要自定義 Bean 後處理器類。該類就是實現了介面 BeanPostProcessor 的類。該介面中包含兩個方法,分別在目標 Bean 初始化完畢之前與之後執行。它們的返回值為:功能被擴充套件或增強後的 Bean 物件。
Bean 初始化完畢有一個標誌:一個方法將被執行。即當該方法被執行時,表示該 Bean被初始化完畢。所以 Bean 後處理器中兩個方法的執行,是在這個方法之前之後執行。這個方法在後面將會講到。
public Object postProcessBeforeInitialization(Object bean, String beanId) throws BeansException
該方法會在目標 Bean 初始化完畢之前由容器自動呼叫。
public Object postProcessAfterInitialization(Object bean, String beanId) throws BeansException
該方法會在目標 Bean 初始化完畢之後由容器自動呼叫。
它們的引數是:第一個引數是系統即將初始化的 Bean 例項,第二個引數是該 Bean 例項的 id 屬性值。若 Bean 沒有 id 就是 name 屬性值。
舉例: ba05 包
程式中有一個業務介面 IService,其有兩個業務方法 some()與 other()。有兩個 Bean:StudentServiceImpl 與 TeacherServiceImpl,均實現了 IService 介面。
要求:對 StudentServiceImpl 的 some()方法進行增強,輸出其開始執行時間與執行結束時間
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

定製 Bean 的生命始末

可以為 Bean 定製初始化後的生命行為,也可以為 Bean 定製銷燬前的生命行為。
舉例:ba06 包。
首先,這些方法需要在 Bean 類中事先定義好:是方法名隨意的 public void 方法。
在這裡插入圖片描述
其次,在配置檔案的標籤中增加如下屬性:
init-method:指定初始化方法的方法名destroy-method:指定銷燬方法的方法名
在這裡插入圖片描述
在這裡插入圖片描述
注意,若要看到 Bean 的 destroy-method 的執行結果,需要滿足兩個條件:
(1)Bean 為 singleton,即單例
(2)要確保容器關閉。介面 ApplicationContext 沒有 close()方法,但其實現類有。所以,可以將 ApplicationContext 強轉為其實現類物件,或直接建立的就是實現類物件。

Bean 的生命週期

Bean 例項從建立到最後銷燬,需要經過很多過程,執行很多生命週期方法。
Step1:呼叫無參構造器,建立例項物件。
Step2:呼叫引數的 setter,為屬性注入值。
Step3:若 Bean 實現了 BeanNameAware 介面,則會執行介面方法 setBeanName(String beanId), 使 Bean 類可以獲取其在容器中的 id 名稱。
Step4:若 Bean 實現了 BeanFactoryAware 介面,則執行介面方法 setBeanFactory(BeanFactory factory),使 Bean 類可以獲取到 BeanFactory 物件。
Step5 : 若 定 義 並 注 冊 了 Bean 後 處 理 器 BeanPostProcessor , 則 執 行 接 口 方 法postProcessBeforeInitialization()。
Step6:若 Bean 實現了 InitializingBean 介面,則執行介面方法 afterPropertiesSet ()。該方法在 Bean 的所有屬性的 set 方法執行完畢後執行,是 Bean 初始化結束的標誌,即 Bean 例項化結束。
Step7:若設定了 init-method 方法,則執行。
Step8 : 若 定 義 並 注 冊 了 Bean 後 處 理 器 BeanPostProcessor , 則 執 行 接 口 方 法postProcessAfterInitialization()。
Step9:執行業務方法。
Step10:若 Bean 實現了 DisposableBean 介面,則執行介面方法 destroy()。
Step11:若設定了 destroy-method 方法,則執行。
舉例:ba07 包

< bean/>標籤的 id 屬性與 name 屬性

一般情況下,命名使用 id 屬性,而不使用 name 屬性。在沒有 id 屬性的情況下,name 屬性與 id 屬性作用是相同的。但,當中含有一些特殊字元時,就需要使用 name屬性了。
id 的命名需要滿足 XML 對 ID 屬性命名規範:必須以字母開頭,可以包含字母、數字、下劃線、連字元、句話、冒號。
name 屬性值則可以包含各種字元。