Spring 核心技術(7)
接上篇:Spring 核心技術(6)
version 5.1.8.RELEASE
1.6 定製 Bean 的特性
Spring Framework 提供了許多可用於自定義 bean 特性的介面。本節將它們分組如下:
- 生命週期回撥
ApplicationContextAware
和BeanNameAware
- 其他
Aware
介面
1.6.1 生命週期回撥
要與容器的 bean 生命週期管理進行互動,可以實現 Spring InitializingBean
和 DisposableBean
介面。容器呼叫前者 afterPropertiesSet()
和後者的 destroy()
以便讓 bean 在初始化和銷燬 bean 時執行某些操作。
JSR-250
@PostConstruct
和@PreDestroy
註解通常被認為是在現代 Spring 應用程式中接收生命週期回撥的最佳實踐。使用這些註解意味著你的 bean 不會耦合到特定的 Spring 介面。有關詳細資訊,請參閱使用 @PostConstruct 和 @PreDestroy。
在內部,Spring Framework 使用 BeanPostProcessor
實現來處理它可以找到的任何回撥介面並呼叫適當的方法。如果你需要自定義其他 Spring 預設不提供的功能或生命週期行為,可以自己實現 BeanPostProcessor
。更多資訊請參閱容器擴充套件點。
除了初始化和銷燬回撥之外,Spring 管理的物件還可以實現 Lifecycle
本節描述了生命週期回撥介面。
初始化回撥
org.springframework.beans.factory.InitializingBean
介面允許 bean 在容器完全設定其所有必要屬性後進行初始化工作。InitializingBean
介面規定了一個方法:
void afterPropertiesSet() throws Exception;
我們建議不要使用 InitializingBean
介面,因為並不需要將程式碼耦合到 Spring。此外,我們建議使用 @PostConstruct
註解或指定 POJO 初始化方法。對於基於 XML 的配置元資料,可以使用 init-method
@Bean
註解的 initMethod
屬性。請參閱接收生命週期回撥。請看以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
前面的示例與以下示例幾乎完全相同(包含兩個列表):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但是,上面兩個示例中,第一個示例沒有將程式碼耦合到 Spring。
銷燬回撥
實現 org.springframework.beans.factory.DisposableBean
介面允許 bean 在包含它的容器被銷燬時獲得回撥。DisposableBean
介面規定了一個方法:
void destroy() throws Exception;
我們建議不要使用 DisposableBean
回撥介面,因為並不需要將程式碼耦合到 Spring。此外,我們建議使用 @PreDestroy
註解或指定 bean 定義支援的泛型方法。使用基於 XML 的配置元資料時,可以使用 <bean/>
元素的 destroy-method
屬性。使用 Java 配置時,可以使用 @Bean
的 destroyMethod
屬性。請參閱接收生命週期回撥。請看以下定義:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定義與以下定義幾乎完全相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,上面兩個示例中,第一個示例沒有將程式碼耦合到 Spring。
你可以為
<bean>
元素的destroy-method
屬性指定一個特殊(推測)值,該值指示 Spring 自動檢測特定 bean 類的公共close
或shutdown
方法。(任何實現java.lang.AutoCloseable或java.io.Closeable
的類都可以進行匹配。)你還可以在<beans>
元素的default-destroy-method
屬性上設定此特殊(推測)值,以將此行為應用於整組 bean(請參閱預設初始化和銷燬方法)。請注意,這是使用 Java 配置時的預設行為。
預設初始化和銷燬方法
當你不使用 Spring 特定的 InitializingBean
和 DisposableBean
回撥介面編寫初始化和銷燬回撥方法時,通常會使用 init()
,initialize()
,dispose()
等來命名方法。理想情況下,此類生命週期回撥方法的名稱在專案中是一致的的,以便所有開發人員使用相同的方法名稱並確保一致性。
你可以將Spring容器配置為在每個 bean 上“查詢”已經命名的初始化和銷燬回撥方法的名稱。這意味著,作為應用程式開發人員,你可以編寫應用程式類並使用 init()
作為初始化回撥 ,而無需為每個 bean 定義配置 init-method="init"
屬性。Spring IoC 容器在建立 bean 時呼叫該方法(根據前面描述的標準生命週期回撥)。此功能還強制執行初始化和銷燬方法回撥的一致命名約定。
假設你的初始化回撥方法已命名為 init()
並且您的銷燬回撥方法已命名為 destroy()
。你的類類似於以下示例:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
你可以在類似於以下內容的 bean 中使用該類:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
頂級 <beans/>
元素上存在 default-init-method
屬性導致 Spring IoC 容器將 bean 類上叫做 init
的方法識別為初始化方法回撥。當 bean 被建立和組裝時,如果 bean 類具有這樣的方法,則會在適當的時候呼叫它。
你可以在XML中同樣通過使用頂級 <beans/>
元素上的 default-destroy-method
屬性來配置銷燬方法回撥。
如果現有的 bean 類已經具有與約定一致的回撥方法,則可以在 XML 中通過使用自身 <bean/>
的 init-method
和 destroy-method
屬性指定方法名稱來覆蓋預設值。
Spring 容器可以保證在為 bean 提供所有依賴項後立即呼叫已配置的初始化回撥。因此,初始化回撥實在原始 bean 引用上呼叫的,這意味著 AOP 攔截器等尚未應用於 bean。首先完全建立目標 bean,然後應用的 AOP 代理(例如帶有攔截器鏈)。如果目標 bean 和代理是分開定義的,那麼你的程式碼甚至可以繞過代理與原始目標 bean 互動。因此,將攔截器應用於 init 方法是不合適的,因為這樣做會將目標 bean 的生命週期耦合到其代理或攔截器,並在程式碼直接與原始目標 bean 互動時表現出奇怪的語義。
合併生命週期機制
從 Spring 2.5 開始,你有三個控制 bean 生命週期行為的選項:
- 在 InitializingBean和 DisposableBean 回撥介面
- 自定義
init()
和destroy()
方法 @PostConstruct
和@PreDestroy
註解。你可以組合這些機制來控制指定的 bean。
如果為 bean 配置了多個生命週期機制,並且每個機制都配置了不同的方法名稱,則每個配置的方法都按照此註釋後列出的順序執行。但是,如果為多個這些生命週期機制配置了相同的方法名稱(例如,給初始化方法命名為
init()
),如上 一節中所述,該方法將執行一次。
為同一個 bean 配置的多個不同的初始化方法時,執行順序如下:
@PostConstruct
註解的方法InitializingBean
定義的afterPropertiesSet()
回撥介面- 自定義配置的
init()
方法
銷燬方法以相同的順序呼叫:
@PreDestroy
註解的方法DisposableBean
定義的destroy()
回撥介面- 自定義配置的
destroy()
方法
啟動和關閉回撥
Lifecycle
介面為任何具有自己的生命週期要求的物件(例如啟動和停止某些後臺程序)定義了基本方法:
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring 管理的物件都可以實現 Lifecycle
介面。然後,當 ApplicationContext
接收到啟動和停止訊號時(例如,對於執行時的停止/重啟場景),它將級聯呼叫上下文中定義的 Lifecycle
的所有實現。它通過委託給 LifecycleProcessor
來實現,如下面的清單所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
請注意,LifecycleProcessor
它本身是 Lifecycle
介面的擴充套件。它還添加了另外兩種方法來響應重新整理和關閉上下文。
請注意,常規的
org.springframework.context.Lifecycle
介面是顯式啟動和停止通知的簡單協議,並不意味著在上下文重新整理時自動啟動。要對特定bean的自動啟動(包括啟動階段)進行細粒度控制,請考慮實現org.springframework.context.SmartLifecycle
。
此外,請注意,在銷燬之前不保證能收到停止通知。在常規關閉時,所有Lifecycle
bean 在一般銷燬回撥開始之前首先收到停止通知。但是,在上下文生命週期中的熱重新整理或中止重新整理嘗試時,僅呼叫銷燬方法。
啟動和關閉呼叫的順序非常重要。如果任何兩個物件之間存在“依賴”關係,則依賴方在其依賴之後開始,並且在其依賴之前停止。但是,有時,直接依賴性是未知的。你可能只知道某種型別的物件應該在另一種型別的物件之前開始。在這些情況下,SmartLifecycle
介面定義了另一個選項,即在其父級介面 Phased
上定義的方法 getPhase()
。以下內容展示了 Phased
介面的定義:
public interface Phased {
int getPhase();
}
以下內容展示了 SmartLifecycle
介面的定義:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
啟動時,具有最低層的物件首先開始。停止時,遵循相反的順序。因此,實現 SmartLifecycle
的物件和 getPhase()
返回的 Integer.MIN_VALUE
將是第一個開始和最後一個停止的物件。在另外一個領域內,相位值 Integer.MAX_VALUE
將指示物件應該最後啟動並首先停止(可能因為它依賴於正在執行的其他程序)。當考慮相位值,同樣重要的是要知道,對於任何“正常”的沒有實現 SmartLifecycle
的 Lifecycle
物件,預設值為 0
。因此,任何負相位值都表示物件應該在這些標準組件之前啟動(並在它們之後停止)。任何正相值都是相反的。
SmartLifecycle
定義的停止方法接受回撥。任何實現必須在該實現的關閉過程完成後呼叫該回調的 run()
方法。這樣就可以在必要時啟用非同步關閉,因為LifecycleProcessor
介面的預設實現 DefaultLifecycleProcessor
等待每個週期內的物件組的超時值來呼叫該回調。預設的每階段超時為30秒。可以通過定義在上下文中命名為 lifecycleProcessor
的 bean 來覆蓋預設生命週期處理器例項 。如果只想修改超時時間,則定義以下內容就足夠了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如之前所述,LifecycleProcessor
介面還定義了用於重新整理和關閉上下文的回撥方法。後者驅動關閉過程類似於顯示呼叫 stop()
,但它只在上下文關閉時發生。另一方面,'refresh' 回撥啟用了 SmartLifecycle
bean的另一個功能 。重新整理上下文時(在例項化並初始化所有物件之後),將呼叫該回調。此時,預設生命週期處理器檢查每個 SmartLifecycle
物件的 isAutoStartup()
方法返回的布林值 。如果是 true
,那麼物件是當時就開始的,而不是等待顯式呼叫上下文或它自己的 start()
方法(與上下文重新整理不同,上下文啟動不會自動發生在標準上下文實現中)。phase
值與任何“依賴式”的關係確定了前面所述的啟動順序。
在非 Web 應用程式中優雅地關閉 Spring IoC 容器
本節僅適用於非 Web 應用程式。Spring 的基於 Web 的
ApplicationContext
實現已經具有相關的程式碼,可以在相關 Web 應用程式關閉時正常關閉 Spring IoC 容器。
如果在非 Web 應用程式環境中使用 Spring 的 IoC 容器(例如,在客戶機桌面環境中),請使用 JVM 註冊關閉鉤子。這樣做可確保正常關閉並在單例 bean 上呼叫相關的銷燬方法,以便釋放所有資源。你必須正確配置和實現這些銷燬回撥。
要註冊關閉鉤子,請呼叫 ConfigurableApplicationContext
介面上宣告的 registerShutdownHook()
方法,如以下示例所示:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2 ApplicationContextAware
和 BeanNameAware
當 ApplicationContext
建立實現 org.springframework.context.ApplicationContextAware
介面的物件例項時,將為 ApplicationContext
提供對該例項的引用。以下清單顯示了 ApplicationContextAware
介面的定義:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以通過 ApplicationContext
介面或通過將引用轉換為此介面的已知子類(例如公開其他功能的 ConfigurableApplicationContext
,)以程式設計方式操縱建立它們的 ApplicationContext
。一種用途是對其他 bean 進行程式設計檢索。有時這種功能很有用。但是,一般情況下應該避免使用它,因為協作者作為屬性提供給 bean 時會將程式碼耦合到 Spring 並且不遵循控制反轉風格。ApplicationContext
的其他方法提供對檔案資源的訪問,釋出應用程式事件以及訪問 MessageSource
。這些附加功能在ApplicationContext
的附加功能中描述 。
從 Spring 2.5 開始,自動裝配是另一種獲取 ApplicationContext
引用的方法。“傳統” constructor
和 byType
自動裝配模式(如自動裝配協作者中所述)可以分別為建構函式引數或 setter 方法引數提供 ApplicationContext
型別的依賴。為了獲得更大的靈活性,以及自動裝配欄位和多引數方法的能力,請使用基於註釋的新自動裝配功能。如果相關的欄位,建構函式或方法帶有 @Autowired
註解且需要 ApplicationContext
型別,ApplicationContext
會自動裝入一個欄位,建構函式引數或方法引數。有關更多資訊,請參閱使用 @Autowired
。
當 ApplicationContext
建立實現 org.springframework.beans.factory.BeanNameAware
介面的類時,將為該類提供其關聯物件定義中對應名稱的引用。以下清單顯示了 BeanNameAware
介面的定義:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回撥在普通 bean 屬性設定之後,但在一個初始化回撥之前,例如 InitializingBean
,afterPropertiesSet
或自定義的初始化方法。
1.6.3 其他 Aware
介面
除了 ApplicationContextAware
和 BeanNameAware
(在之前討論過),Spring 提供了廣泛的 Aware
回撥讓 bean 向容器指出他們需要一定的基礎設依賴。作為一般規則,名稱表示依賴關係型別。下表總結了最重要的 Aware
介面:
名稱 | 注入依賴 | 描述 |
---|---|---|
ApplicationContextAware | 宣告 ApplicationContext |
ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware | 包含 ApplicationContext 的時間釋出者 | ApplicationContext 的其他功能 |
BeanClassLoaderAware | 用於載入bean類的類載入器 | 例項化 Bean |
BeanFactoryAware | 宣告 BeanFactory | ApplicationContextAware 和 BeanNameAware |
BeanNameAware | 宣告 bean 的名稱 | ApplicationContextAware 和 BeanNameAware |
BootstrapContextAware | 容器執行的資源介面卡 BootstrapContext 。通常僅在支援 JCA 的 ApplicationContext 例項中可用 |
JCA CCI |
LoadTimeWeaverAware | 定義在載入時用於處理類定義的 weaver | 在 Spring 框架中使用 AspectJ 進行載入時織入 |
MessageSourceAware | 用於解析訊息的已配置策略(支援引數化和國際化) | ApplicationContext 的其他功能 |
NotificationPublisherAware | Spring JMX 通知釋出者 | 通知 |
ResourceLoaderAware | 用於對低層次資源進行訪問的配置載入器 | 資源 |
ServletConfigAware | 當前容器執行的 ServletConfig 。僅在支援 Web 的 ApplicationContext 中有效 |
Spring MVC |
ServletContextAware | 當前容器執行的 ServletContext 。僅在支援 Web 的 ApplicationContext 中有效 |
Spring MVC |
請再次注意,使用這些介面會使你的程式碼與 Spring API 耦合,而且不會遵循 IOC 規範。因此,我們建議將它們用於需要以程式設計方式訪問容器的基礎架構 bean。
- 我的CSDN:https://blog.csdn.net/liweitao7610
- 我的部落格園:https://www.cnblogs.com/aotian/
- 我的簡書:https://www.jianshu.com/u/6b6e162f1fdc