前言
深入理解DIP、IoC、DI以及IoC容器:https://www.cnblogs.com/liuhaorain/p/3747470.html
為什么要使用IOC
傳統開發模式的弊端
三層架構是經典的開發模式,我們一般將視圖控制、業務邏輯和數據庫操作分別抽離出來單獨形成一個類,這樣各個職責就非常清晰且易于復用和維護。大致代碼如下:
用戶DAO
public class UserDAO {
private String database;
public UserDAO(String dataBase) {
this.database = dataBase;
}
public void doSomething() {
System.out.println("保存用戶!");
}
}
用戶Service
public class UserService {
private UserDAO dao;
public UserService(UserDAO dao) {
this.dao = dao;
}
public void doSomething() {
dao.doSomething();
}
}
用戶Controller
public class Controller {
public UserService service;
public Controller(UserService userService) {
this.service = userService;
}
public void doSomething() {
service.doSomething();
}
}
接下來我們就必須手動一個一個創建對象,并將dao、service、controller依次組裝起來,然后才能調用。
public static void main(String[] args) {
UserDAO dao = new UserDAO("mysql");
UserService service = new UserService(dao);
Controller controller = new Controller(service);
controller.doSomething();
}
分析一下這種做法的弊端有哪些呢?
- 在生成Controller的地方我們都必須先創建dao再創建service最后再創建Controller,這么一條繁瑣的創建過程。
- 在這三層結構當中,上層都需要知道下層是如何創建的,上層必須自己創建下層,這樣就形成了緊密耦合。為什么業務程序員在寫業務的時候卻需要知道數據庫的密碼并自己創建dao呢?不僅如此,當如果dao的數據庫密碼變化了,在每一處生成Controller的地方都需要進行修改。
- 通過new關鍵字生成了具體的對象,這是一種硬編碼的方式,違反了面向接口編程的原則。當有一天我們從Hibernate更換到Mybatis的時候,在每一處new DAO的地方,我們都需要進行更換。
- 我們頻繁的創建對象,浪費了資源。
這時候我們再來看看如果用SpringIOC的情況,剛才的代碼變成如下。
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
Controller controller = (Controller) context.getBean("controller");
controller.doSomething();
}
很明顯,使用IOC之后,我們只管向容器索取所需的Bean即可。IOC便解決了以下的痛點:
- Bean之間的解耦,這種解耦體現在我們沒有在代碼中去硬編碼bean之間的依賴。(不通過new操作依次構建對象,由springIOC內部幫助我們實現了依賴注入)。一方面,IOC容器將通過new對象設置依賴的方式轉變為運行期動態的進行設置依賴。
- IOC容器天然地給我們提供了單例。
- 當需要更換dao的時候,我們只需要在配置文件中更換dao的實現類,完全不會破壞到之前的代碼。
- 上層現在不需要知道下層是如何創建的。
Spring IoC容器
IoC容器是Spring的核心,也可以稱為Spring容器。Spring 通過 IoC 容器來管理對象的實例化和初始化,以及對象從創建到銷毀的整個生命周期。
Spring 中使用的對象都由 IoC 容器管理,不需要我們手動使用 new 運算符創建對象。由 IoC 容器管理的對象稱為 Spring Bean,Spring Bean 就是 Java 對象,和使用 new 運算符創建的對象沒有區別。
Spring 通過讀取 XML 或 Java 注解中的信息來獲取哪些對象需要實例化。
Spring 提供 2 種不同類型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。
BeanFactory 容器
BeanFactory 是最簡單的容器,由 org.springframework.beans.factory.BeanFactory 接口定義,采用懶加載(lazy-load),所以容器啟動比較快。BeanFactory 提供了容器最基本的功能。
為了能夠兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了該接口。
簡單來說,BeanFactory 就是一個管理 Bean 的工廠,它主要負責初始化各種 Bean,并調用它們的生命周期方法。
BeanFactory 接口有多個實現類,最常見的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要創建 XmlBeanFactory 類的實例,通過 XmlBeanFactory 類的構造函數來傳遞 Resource 對象。如下所示。
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);
ApplicationContext 容器
ApplicationContext 繼承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定義,對象在啟動容器時加載。ApplicationContext 在 BeanFactory 的基礎上增加了很多企業級功能,例如 AOP、國際化、事件支持等。
ApplicationContext 接口有兩個常用的實現類,具體如下。
ClassPathXmlApplicationContext
該類從類路徑 ClassPath 中尋找指定的 XML 配置文件,并完成 ApplicationContext 的實例化工作,具體如下所示。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
在上述代碼中,configLocation 參數用于指定 Spring 配置文件的名稱和位置,如 Beans.xml。
FileSystemXmlApplicationContext
該類從指定的文件系統路徑中尋找指定的 XML 配置文件,并完成 ApplicationContext 的實例化工作,具體如下所示。
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
它與 ClassPathXmlApplicationContext 的區別是:在讀取 Spring 的配置文件時,FileSystemXmlApplicationContext 不會從類路徑中讀取配置文件,而是通過參數指定配置文件的位置。即 FileSystemXmlApplicationContext 可以獲取類路徑之外的資源,如“F:/workspaces/Beans.xml”。
通常在 Java 項目中,會采用 ClassPathXmlApplicationContext 類實例化 ApplicationContext 容器的方式,而在 Web 項目中,ApplicationContext 容器的實例化工作會交由 Web 服務器完成。Web 服務器實例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 實現的方式,它只需要在 web.xml 中添加如下代碼:
<!--指定Spring配置文件的位置,有多個配置文件時,以逗號分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring將加載spring目錄下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式啟動Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
需要注意的是,BeanFactory 和 ApplicationContext 都是通過 XML 配置文件加載 Bean 的。
二者的主要區別在于,如果 Bean 的某一個屬性沒有注入,使用 BeanFacotry 加載后,第一次調用 getBean() 方法時會拋出異常,而 ApplicationContext 則會在初始化時自檢,這樣有利于檢查所依賴的屬性是否注入。
因此,在實際開發中,通常都選擇使用 ApplicationContext,只有在系統資源較少時,才考慮使用 BeanFactory。
Spring Bean定義
由 Spring IoC 容器管理的對象稱為 Bean,Bean 根據 Spring 配置文件中的信息創建。
可以把 Spring IoC 容器看作是一個大工廠,Bean 相當于工廠的產品,如果希望這個大工廠生產和管理 Bean,則需要告訴容器需要哪些 Bean,以及需要哪種方式裝配 Bean。
Spring 配置文件支持兩種格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 鍵值對的形式存在,只能賦值,不能進行其他操作,適用于簡單的屬性配置。
- XML 配置文件是樹形結構,相對于 Properties 文件來說更加靈活。XML 配置文件結構清晰,但是內容比較繁瑣,適用于大型復雜的項目。
通常情況下,Spring 的配置文件使用 XML 格式。XML 配置文件的根元素是 <beans>
,該元素包含了多個子元素 <bean>
。每一個 <bean>
元素都定義了一個 Bean,并描述了該 Bean 如何被裝配到 Spring 容器中。
在上一節中的 Beans.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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld">
<property name="message" value="Hello World!" />
</bean>
</beans>
解釋
-
xmlns="http://www.springframework.org/schema/beans"
聲明xml文件默認的命名空間,表示未使用其他命名空間的所有標簽的默認命名空間。
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
聲明XML Schema實例,聲明后就可以使用schemaLocation屬性。
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd“指定Schema的位置這個屬性必須結合命名空間使用。這個屬性有兩個值,第一個值表示需要使用的命名空間。第二個值表示供命名空間使用的XML schema的位置。
上面配置的命名空間指定xsd規范文件,這樣你在進行下面具體配置的時候就會根據這些xsd規范文件給出相應的提示,比如說每個標簽是怎么寫的,都有些什么屬性是都可以智能提示的,在啟動服務的時候也會根據xsd規范對配置進行校驗。
配置技巧:對于屬性值的寫法是有規律的,中間使用空格隔開,后面的值是前面的補充,也就是說,前面的值是去除了xsd文件后得來的。
上述代碼中,使用 id 屬性定義了 Bean,并使用 class 屬性指定了 Bean 對應的類。
<bean>
元素中可以包含很多屬性,其常用屬性如下表所示。
屬性名稱 | 描述 |
---|---|
id | Bean 的唯一標識符,Spring 容器對 Bean 的配置和管理都通過該屬性完成。id 的值必須以字母開始,可以使用字母、數字、下劃線等符號。 |
name | name 屬性中可以為 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開。Spring 容器可以通過 name 屬性配置和管理容器中的 Bean。 |
class | 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。 |
scope | 用于設定 Bean 實例的作用域,屬性值可以為 singleton(單例)、prototype(原型)、request、session 和 global Session。其默認值是 singleton |
constructor-arg | |
property | |
ref | |
value | |
list | 用于封裝 List 或數組類型的依賴注入 |
set | 用于封裝 Set 類型的依賴注入 |
map | 用于封裝 Map 類型的依賴注入 |
entry | |
init-method | 容器加載 Bean 時調用該方法,類似于 Servlet 中的 init() 方法 |
destroy-method | 容器刪除 Bean 時調用該方法,類似于 Servlet 中的 destroy() 方法。該方法只在 scope=singleton 時有效 |
lazy-init | 懶加載,值為 true,容器在首次請求時才會創建 Bean 實例;值為 false,容器在啟動時創建 Bean 實例。該方法只在 scope=singleton 時有效 |
Spring Bean作用域
Bean的生命周期,從創建到銷毀。
在配置文件中,除了可以定義 Bean 的屬性值和相互之間的依賴關系,還可以聲明 Bean 的作用域。例如,如果每次獲取 Bean 時,都需要一個 Bean 實例,那么應該將 Bean 的 scope 屬性定義為 prototype,如果 Spring 需要每次都返回一個相同的 Bean 實例,則應將 Bean 的 scope 屬性定義為 singleton。
作用域的種類
Spring 容器在初始化一個 Bean 實例時,同時會指定該實例的作用域。Spring 5 支持以下 6 種作用域。
-
singleton
默認值,單例模式,表示在 Spring 容器中只有一個 Bean 實例,Bean 以單例的方式存在。
-
prototype
原型模式,表示每次通過 Spring 容器獲取 Bean 時,容器都會創建一個 Bean 實例。
-
request
每次 HTTP 請求,容器都會創建一個 Bean 實例。該作用域只在當前 HTTP Request 內有效。
-
session
同一個 HTTP Session 共享一個 Bean 實例,不同的 Session 使用不同的 Bean 實例。該作用域僅在當前 HTTP Session 內有效。
-
application
同一個 Web 應用共享一個 Bean 實例,該作用域在當前 ServletContext 內有效。
類似于 singleton,不同的是,singleton 表示每個 IoC 容器中僅有一個 Bean 實例,而同一個 Web 應用中可能會有多個 IoC 容器,但一個 Web 應用只會有一個 ServletContext,也可以說 application 才是 Web 應用中貨真價實的單例模式。
-
websocket
websocket 的作用域是 WebSocket ,即在整個 WebSocket 中有效。
注意:Spring 5 版本之前還支持 global Session,該值表示在一個全局的 HTTP Session 中,容器會返回該 Bean 的同一個實例。一般用于 Portlet 應用環境。Spring 5.2.0 版本中已經將該值移除了。
request、session、application、websocket 和 global Session 作用域只能在 Web 環境下使用,如果使用 ClassPathXmlApplicationContext 加載這些作用域中的任意一個的 Bean,就會拋出以下異常。
java.lang.IllegalStateException: No Scope registered for scope name 'xxx'
下面我們詳細講解常用的兩個作用域:singleton 和 prototype。
singleton
singleton 是 Spring 容器默認的作用域。
當 Bean 的作用域為 singleton 時,Spring 容器中只會存在一個共享的 Bean 實例。該 Bean 實例將存儲在高速緩存中,并且所有對 Bean 的請求,只要 id 與該 Bean 定義相匹配,都會返回該緩存對象。
通常情況下,這種單例模式對于無會話狀態的 Bean(如 DAO 層、Service 層)來說,是最理想的選擇。
在 Spring 配置文件中,可以使用 <bean>
元素的 scope 屬性,將 Bean 的作用域定義成 singleton,其配置方式如下所示:
<bean id="..." class="..." scope="singleton"/>
例子
我們重新改裝MainApp類
package com.springlearn;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setMessage("對象A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
由于Spring 容器默認的作用域是singleton。
所以Bean.xml可以不用修改,當前你也可以加上scope
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" scope="singleton" />
</beans>
運行結果如下
message : 對象A
message : 對象A
從運行結果可以看出,兩次輸出內容相同,這說明 Spring 容器只創建了一個 HelloWorld 類的實例。由于 Spring 容器默認作用域是 singleton,所以如果省略 scope 屬性,其輸出結果也會是一個實例。
prototype
瞬時
對于 prototype 作用域的 Bean,Spring 容器會在每次請求該 Bean 時都創建一個新的 Bean 實例。prototype 作用域適用于需要保持會話狀態的 Bean(如 Struts2 的 Action 類)。
在 Spring 配置文件中,可以使用
<bean id="..." class="..." scope="prototype"/>
例子
修改配置文件 Beans.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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" scope="prototype" />
</beans>
運行結果如下
message : 對象A
message : null
從運行結果可以看出,兩次輸出的內容并不相同,這說明在 prototype 作用域下,Spring 容器創建了兩個不同的 HelloWorld 實例。
Spring Bean生命周期
在傳統的 Java 應用中,Bean 的生命周期很簡單,使用關鍵字 new 實例化 Bean,當不需要該 Bean 時,由 Java 自動進行垃圾回收。
Spring 中 Bean 的生命周期為:Bean 的定義 -> Bean 的初始化 -> Bean 的使用 -> Bean 的銷毀。
Spring 根據 Bean 的作用域來選擇管理方式。對于 singleton 作用域的 Bean,Spring 能夠精確地知道該 Bean 何時被創建,何時初始化完成,以及何時被銷毀;而對于 prototype 作用域的 Bean,Spring 只負責創建,當容器創建了 Bean 的實例后,Bean 的實例就交給客戶端代碼管理,Spring 容器將不再跟蹤其生命周期。
Spring Bean生命周期執行流程
Spring 容器在確保一個 Bean 能夠使用之前,會進行很多工作。Spring 容器中 Bean 的生命周期流程如下圖所示。
Bean 生命周期的整個執行過程描述如下。
- Spring 啟動,查找并加載需要被 Spring 管理的 Bean,并實例化 Bean。
- 利用依賴注入完成 Bean 中所有屬性值的配置注入。
- 如果 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。
- 如果 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。
- 如果 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。
- 如果 Bean 實現了 BeanPostProcessor 接口,則 Spring 調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。
- 如果 Bean 實現了 InitializingBean 接口,則 Spring 將調用 afterPropertiesSet() 方法。
- 如果在配置文件中通過 init-method 屬性指定了初始化方法,則調用該初始化方法。
- 如果 BeanPostProcessor和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization()。此時,Bean 已經可以被應用系統使用了。
- 如果在
中指定了該 Bean 的作用域為 singleton,則將該 Bean 放入 Spring IoC 的緩存池中,觸發 Spring 對該 Bean 的生命周期管理;如果在 中指定了該 Bean 的作用域為 prototype,則將該 Bean 交給調用者,調用者管理該 Bean 的生命周期,Spring 不再管理該 Bean。 - 如果 Bean 實現了 DisposableBean 接口,則 Spring 會調用 destory() 方法銷毀 Bean;如果在配置文件中通過 destory-method 屬性指定了 Bean 的銷毀方法,則 Spring 將調用該方法對 Bean 進行銷毀。
Spring 為 Bean 提供了細致全面的生命周期過程,實現特定的接口或設置
了解 Spring 生命周期的意義就在于,可以利用 Bean 在其存活期間的指定時刻完成一些相關操作。一般情況下,會在 Bean 被初始化后和被銷毀前執行一些相關操作。
Spring 官方提供了 3 種方法實現初始化回調和銷毀回調:
- 實現 InitializingBean 和 DisposableBean 接口;
- 在 XML 中配置 init-method 和 destory-method;
- 使用 @PostConstruct 和 @PreDestory 注解。
在一個 Bean 中有多種生命周期回調方法時,優先級為:注解 > 接口 > XML。
不建議使用接口和注解,這會讓 pojo 類和 Spring 框架緊耦合。
初始化回調
-
使用接口
修改
HelloWorld
類package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld implements InitializingBean { private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } @Override public void afterPropertiesSet() throws Exception { System.out.printf("調用接口:InitializingBean,方法:afterPropertiesSet,無參數"); } }
調用接口:InitializingBean,方法:afterPropertiesSet,無參數message : 對象A 調用接口:InitializingBean,方法:afterPropertiesSet,無參數message : null
-
配置XML
bean.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-3.0.xsd"> <bean id="helloWorld" class="com.springlearn.HelloWorld" init-method="init" scope="prototype" /> </beans>
添加
init()
方法package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld{ private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } public void init() { System.out.println("調用init-method指定的初始化方法:init" ); } }
-
使用注解
使用 @PostConstruct 注解標明該方法為 Bean 初始化后的方法。
public class HelloWrold { @PostConstruct public void init() { System.out.println("@PostConstruct注解指定的初始化方法:init" ); } }
使用
@PostConstruct
注解需要引入javax.annotation
在
pom.xml
中添加<dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency>
銷毀回調
-
使用接口
org.springframework.beans.factory.DisposableBean 接口提供了以下方法:
void destroy() throws Exception;
您可以實現以上接口,在 destroy 方法內指定 Bean 初始化后需要執行的操作。
<bean id="..." class="..." /> public class HelloWrold implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("調用接口:InitializingBean,方法:afterPropertiesSet,無參數"); } }
-
配置XML
<bean id="..." class="..." destroy-method="destroy"/> public class HelloWrold { public void destroy() { System.out.println("調用destroy-method指定的銷毀方法:destroy" ); } }
-
使用注解
使用 @PreDestory 注解標明該方法為 Bean 銷毀前執行的方法。
public class HelloWrold { @PreDestory public void init() { System.out.println("@PreDestory注解指定的初始化方法:destroy" ); } }
示例
HelloWorld
package com.springlearn;
public class HelloWorld{
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("message : " + message);
}
public void init() {
System.out.println("Bean正在進行初始化");
}
public void destroy() {
System.out.println("Bean將要被銷毀");
}
}
Bean.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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
</beans>
MainApp
package com.springlearn;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Thread.currentThread();
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage();
//銷毀容器
context.registerShutdownHook();
}
}
Bean正在進行初始化
message : Hello World!
Bean將要被銷毀
默認的初始化和銷毀方法
如果多個 Bean 需要使用相同的初始化或者銷毀方法,不用為每個 bean 聲明初始化和銷毀方法,可以使用 default-init-method 和 default-destroy-method 屬性,如下所示。
<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-3.0.xsd"
default-init-method="init"
default-destroy-method="destroy">
<bean id="..." class="...">
...
</bean>
</beans>
BeanPostProcessor(Spring后置處理器)
BeanPostProcessor 接口也被稱為后置處理器,通過該接口可以自定義調用初始化前后執行的操作方法。
BeanPostProcessor 接口源碼如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
postProcessBeforeInitialization 在 Bean 實例化、依賴注入后,初始化前調用。postProcessAfterInitialization 在 Bean 實例化、依賴注入、初始化都完成后調用。
當需要添加多個后置處理器實現類時,默認情況下 Spring 容器會根據后置處理器的定義順序來依次調用。也可以通過實現 Ordered 接口的 getOrder 方法指定后置處理器的執行順序。該方法返回值為整數,默認值為 0,值越大優先級越低。
新建InitHelloWorld 類代碼如下
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 5;
}
}
需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能為 null,否則會報空指針異?;蛘咄ㄟ^ getBean() 方法獲取不到 Bean 實例對象,因為后置處理器從Spring IoC 容器中取出 Bean 實例對象后沒有再次放回到 IoC 容器中。
InitHelloWorld2 的代碼如下。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld2 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
beans.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-3.0.xsd">
<bean id="helloWorld" class="net.biancheng.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
<!-- 注冊處理器 -->
<bean class="net.biancheng.InitHelloWorld" />
<bean class="net.biancheng.InitHelloWorld2" />
</beans>
MainApp
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}
}
B Before : helloWorld
A Before : helloWorld
Bean正在初始化
B After : helloWorld
A After : helloWorld
Message : Hello World!
Bean將要被銷毀
由運行結果可以看出,postProcessBeforeInitialization 方法是在 Bean 實例化和依賴注入后,自定義初始化方法前執行的。而 postProcessAfterInitialization 方法是在自定義初始化方法后執行的。由于 getOrder 方法返回值越大,優先級越低,所以 InitHelloWorld2 先執行。