Spring
框架提供一些接口,你可以使用這些接口去自定義bean
的性質。這個章節包括下面內容:
- 生命週期回調
-
ApplicationContextAware
和BeanNameAwar
-
其他的
Aware
接口
1.6.1 生命週期回調
為了與容器的bean
的生命週期的管理交互,你可以實現Spring
提供的InitializingBean
和DisposableBean
接口。容器為前者調用afterPropertiesSet()
併為後者調用destroy()
,以使Bean
在初始化和銷燬Bean
時執行某些操作。
在現代化的
Spring
應用中,JSR-250
的@PostConstruct
和@PreDestroy
註解是一般被考慮的最好實踐去接收生命週期回調。使用這些註解意味著你的這些bean
不需要耦合Spring
規範接口。更多詳情,查看@PostConstruct
和@PreDestroy
如果你不想去使用
JSR-250
註解,但是你仍然想移除耦合,考慮init-method和destroy-method
bean的元數據定義。
內部地,Spring
框架使用BeanPostProcessor
實現去處理任何的回調接口,它能找到和調用適合的方法。如果你需要定製特性或者其他的生命週期行為Spring
默認沒有提供,你需要自己實現一個BeanPostProcessor
。關於更多的信息,查看容器擴展點。
除了初始化和銷燬回調外,Spring
管理的對象還可以實現Lifecycle
接口以便這些對象可以在容器自身的生命週期的驅動下參與啟動和關閉過程。
生命週期回調接口在這章節中描述。
- 初始化回調
org.springframework.beans.factory.InitializingBean
接口允許在bean
的所有屬性被設置之前執行初始化工作。InitializingBean
接口有一個簡單方法:
void afterPropertiesSet() throws Exception;
我們推薦你不要去使用InitializingBean
接口,因為它不必要的耦合Spring
。或者,我們建議使用@PostConstruct
註解或者指定一個POJO
初始化方法。在基於XML
配置元數據實例中,你可以使用init-method
屬性去指定方法的名稱並且方法沒有返回值和無參數簽名。在JavaConfig
中,你可以使用@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 {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
然而,前面兩個例子中的第一個沒有耦合Spring
代碼。
參考代碼:
com.liyong.ioccontainer.starter.XmlInitializationIocContainer
- 銷燬回調
當包含bean
的容器被銷燬時,實現org.springframework.beans.factory.DisposableBean
接口的bean
獲得回調。DisposableBean
接口有個簡單方法:
void destroy() throws Exception;
我們推薦你不要使用DisposableBean
回調接口,因為它不必要的耦合Spring
代碼。或者,我們建議使用 @PreDestroy
註解或者指定一個通用方法通過bean
的定義支持。基於XML
的配置元數據,你可以在上使用destroy-method
屬性。通過JavaConfig
配置,你可以使用@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 {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
然而,前面兩個定義中的第一個沒有耦合Spring代碼。
你可以賦值元素的
destroy-method
屬性值,這指示Spring
自動的在bean
類上檢測一個公共的close
或者shutdown
方法。(因此,任何實現java.lang.AutoCloseable
或java.io.Closeable
的類都將匹配)你可以在元素的default-destroy-method
屬性設置這個指定的值去應用這個行為到整個bean
容器(查看默認初始化和銷燬方法)。注意:這個一個默認行為在JavaConfig
中。參考代碼:
com.liyong.ioccontainer.starter.XmlDestuctionIocContainer
- 默認初始化和銷燬方法
當你寫初始化和銷燬方法回調的時候,不要使用Spring
指定的InitializingBean
和DisposableBean
回調接口,典型的命名例如:init()
、initialize()
、dispose()
等等。理想地,生命週期回調方法的命名在項目中被標註化,因此所有的開發者使用項目的名字確保一致性。
你可以配置Spring
框架容器去在每個bean
查找被命名初始化和銷燬回調方法名稱。作為一個應用開發者,這意味著你可以寫你的應用類和使用初始化回調調用init()
,沒有必要配置每個bean
的定義屬性init-method="init"
。當bean
被創建時(並按照之前描述的標準生命週期回調),Spring IoC
容器調用方法。這個特性強調為初始化和銷燬回調方法命名的一致性。
假設你的初始化回調方法被命名為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>
頂層元素屬性default-init-method
中描述導致Spring IoC
容器在bean
的類上去識別一個叫做init
的方法作為初始化方法回調。當bean
被創建和組裝好,如果bean
的類有一個方法,它在適當的時候被調用。
你可以配置銷燬方法回調類似(在XML
)的通過使用在頂層元素上的default-destroy-method
方法。
現有的Bean
類已經具有回調方法的名稱,這些方法的命名方式與約定不符,你可以通過元素屬性init-method
和destroy-method
指定方法名稱。
Spring
容器保證配置初始化方法在bean
提供所有依賴後立即被調用。因此,在原始bean
引用上調用初始化回調,這意味著AOP
攔截器等還沒有應用到bean
上。首先完全創建目標bean
,然後應用帶有其攔截器鏈的AOP
代理。如果目標Bean
和代理分別定義,則你的代碼甚至可以繞過代理與原始目標Bean
進行交互。因此,將攔截器應用於init
方法是不一致的,因為這樣做會將目標bean
的生命週期耦合到它的代理或攔截器,並在代碼直接與原始目標bean
交互時留下奇怪的語義。
- 組合生命週期機制
從Spring2.5後,你有三種可以選擇的方式去控制生命週期行為:
-
InitializingBean
和DisposableBean
回調接口 - 自定義
init()
和destroy()
-
@PostConstruct
和@PreDestroy
註解.。你可以組合這些機制去控制bean
。
如果為bean配置多個生命週期機制並且每個機制被配置不同的方法名稱,每個配置的方法都按照此註釋後列出的順序執行。但是,如果為多個生命週期機制中的多個生命週期機制配置了相同的方法名稱(例如,為初始化方法使用
init()
),則該方法將執行一次,如上一節所述。
為同一個bean
配置的多種生命週期機制具有不同的初始化方法,如下所示:
- 方法註解
@PostConstruct
- 通過
InitializingBean
回調接口定義afterPropertiesSet()
- 自定義配置
init()
方法
銷燬方法的調用順序相同:
- 方法註解
@PreDestroy
- 通過
DisposableBean
回調接口定義destroy()
- 自定義配置
destroy()
方法
- 啟動和關閉回調
Lifecycle
接口定義了必須要的方法為這些bean
生命週期所必須(例如:啟動和停止一些後臺處理)。
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring
管理的對象可能實現Lifecycle
接口。然而,當ApplicationContext
接收到啟動和停止信號(例如:在運行時停止和重啟場景),它將這些調用級聯到在該上下文中定義的所有生命週期實現。通過代理到LifecycleProcessor
處理,在下面清單顯示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意:LifecycleProcessor
是Lifecycle
接口的拓展。這個接口增加了兩個額外的方法去接收上下文的刷新和關閉。
注意:
常規的
org.springframework.context
。生命週期接口是顯式啟動和停止通知的普通契約,並不意味著在上下文刷新時自動啟動。為了更細粒度控制bean
的自動啟動(包括啟動階段),考慮使用org.springframework.context.SmartLifecycle
替換。停止通知不會被保證在銷燬之前到來。在常規的關閉上,所有
Lifecycle
的bean
第一次接受停止通知在銷燬回調被傳播之前。但是,在上下文生命週期中進行熱更新或更新嘗試失敗時,僅調用destroy
方法。
啟動和關閉順序調用是非常重要的。如果依賴關係存在任何對象之間,依賴側開始在被依賴之後並且它的停止在它的依賴之前。然而,在運行時,這個直接依賴是未知的。你可能只知道某種類型的對象應該先於另一種類型的對象開始。在這種情況下,SmartLifecycle
接口定義其他可選,即getPhase()
方法在它的父接口被定義,Phased
。下面的清單顯示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
的“正常”生命週期對象的默認階段為0
。因此,任何負的階段值表示對象應該啟動在這些標準的組件之前(停止在它們之後)。對於任何正階段值,情況正好相反。
停止方法通過SmartLifecycle
接受一個回調被定義。任何實現必須在實現的關閉處理完成後調用回調的run()
方法。這將在必要時啟用異步關閉,因為LifecycleProcessor
接口的默認實現DefaultLifecycleProcessor
會等待其超時值,以等待每個階段內的對象組調用該回調。每個階段默認超時時間30秒。你可以通過定義一個bean
命名為lifecycleProcessor
在上下文中覆蓋這個默認生命週期處理實例。如果你想僅僅修改超時時間,定義以下內容就足夠了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
像前面提到的,LifecycleProcessor
接口定義回調方法為上下文更好的刷新和關閉。後者驅動關閉過程,就好像已經顯式調用了stop()
一樣,但是它在上下文關閉時發生。另一方面,“refresh
”回調啟用了SmartLifecycle
bean
的另一個特性。當這個上下文被刷新(所有的bean
被初始化和實例化),即回調被調用。在那個階段,默認的生命週期處理器通過每個SmartLifecycle
對象的isAutoStartup()
方法檢測返回boolean
值。如果true
,對象在那個階段被啟動而不是等待上下文或者自身的start()
方法顯示調用(與上下文刷新不同,對於標準上下文實現,上下文啟動不會自動發生)。像前面所描述的,phase
值和任何依賴關係確定了啟動順序。
參考代碼:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
- 在非Web應用程序中優雅關閉Spring IoC容器
這個部分僅僅適用非
web
應用程序。Spring
的基於Web
的ApplicationContext
實現提供優雅的關閉Spring IoC
容器,當相關的web
應用程序關閉的時候。
如果你使用Spring
的IoC
容器在非web
應用環境(例如,在一個富客戶端桌面環境),在JVM
註冊一個關閉鉤子。這樣做確保優雅的關閉和調用在單例bean
上的銷燬方法,因此所有資源被釋放。你必須仍然正確地配置和實現這些銷燬回調。
去註冊一個關閉鉤子,調用registerShutdownHook()
方法是在ConfigurableApplicationContext
接口上聲明,如下例子:
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");
//增加一個Hook構造回調
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
創建它們,通過ApplicationContext
接口或者轉換引用為已知的這個接口的子類(例如:ConfigurableApplicationContext
,它暴露了附加的功能)。一種用途是通過編程方式檢索其他bean
。有時候這個能力非常有用。然而,一般的,你應該避免它,因為它耦合Spring
的代碼並且不遵循控制反轉格式,將協調者作為bean
的屬性。ApplicationContext
其他方法提供獲取資源文件、發佈應用事件和訪問MessageSource
。這些額外的特性在ApplicationContext的附加能力中描述。
自動裝配是獲得對ApplicationContext
的引用的另一種選擇。傳統的constructor
和byType
自動裝配模式(在自動裝配協作器中描述)能夠為構造函數或者Setter
方法參數提供一個ApplicationContext
類型的依賴。為了獲得更大的靈活性,包括自動裝配字段和多個參數方法的能力,可以使用基於註解的自動裝配特性。如果這樣做,ApplicationContext
自動裝配到字段、構造參數或方法參數,如果字段、構造函數或方法帶有@Autowired
註解那麼該參數期望ApplicationContext
類型。更多的信息,查看使用@Autowired。
當ApplicationContext
創建一個類並且實現org.springframework.beans.factory.BeanNameAware
接口時,該類將獲得對其關聯對象定義中定義的名稱的引用。下面清單顯示BeanNameAware
接口定義:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
這個回調在bean
被填充之後被調用,但是在初始化回調之前例如InitializingBean
, afterPropertiesSet
或者自定義init-method
。
參考代碼:
com.liyong.ioccontainer.starter.XmlAwareIocContainer
1.6.3 其他Aware接口
除了ApplicationContextAware
和BeanNameAware
之外,Spring
提供了廣泛的Aware
回調接口,這些接口使Bean
向容器指示它們需要一些基礎結構依賴性。作為基本規則,這個名字指示依賴類型。下面的表格總結最重要的Aware
接口:
Name | Injected Dependency | Explained in… |
---|---|---|
ApplicationContextAware |
注入 ApplicationContext . |
ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware |
注入ApplicationEventPublisher . |
Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware |
Class loader used to load the bean classes. | Instantiating Beans |
BeanFactoryAware |
注入 BeanFactory . |
ApplicationContextAware and BeanNameAware |
BeanNameAware |
注入BeanName
|
ApplicationContextAware and BeanNameAware |
BootstrapContextAware |
注入BootstrapContext
|
JCA CCI |
LoadTimeWeaverAware |
注入LoadTimeWeaver . |
Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware |
注入MessageSource
|
Additional Capabilities of the ApplicationContext |
NotificationPublisherAware |
注入NotificationPublisher . |
Notifications |
ResourceLoaderAware |
注入ResourceLoader | Resources |
ServletConfigAware |
注入ServletConfig . |
Spring MVC |
ServletContextAware |
注入ServletContext . |
Spring MVC |
再次注意,使用這些接口關聯到你的代碼到Spring API
並且不遵循控制反轉的風格。我們推薦使用基礎設施bean
需要編程的去容器獲取。
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公眾號:
技術交流群: