3.6 Customizing the nature of a bean
3.6.1 生命周期回调函数
要与容器的bean生命周期管理进行交互,可以实现SpringInitializingBean
和DisposableBean
接口。 容器为前者调用beforePropertiesSet()
,后者调用destroy()
,以允许bean在初始化和销毁bean时执行某些操作。
JSR-250@PostConstruct 和@PreDestroy 注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。 使用这些注解意味着你的bean不耦合到Spring特定的接口。 有关详细信息,请参见第3.9.8节@PostConstruct和@PreDestroy 。如果你不想使用JSR-250注解,但是你仍然希望删除耦合考虑使用init-method和destroy-method对象定义元数据. |
在内部,Spring框架使用BeanPostProcessor
实现来处理它可以找到并调用适当方法的任何回调接口。 如果你需要自定义特性或其他生命周期行为但Spring不提供开箱即用的,你可以自己实现一个BeanPostProcessor
。 有关详细信息,请参见第3.8节容器扩展点
.
除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle
接口,使得这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
本节介绍了生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许bean在容器已经设置好bean的所有必需属性之后执行初始化工作。 InitializingBean
接口指定了一个单一的方法:
void afterPropertiesSet() throws Exception;
建议不要使用InitializingBean
接口,因为它不必要地将代码耦合到Spring。 或者,使用@PostConstruct
注解或指定POJO初始化方法。 在基于XML的配置元数据的情况下,你使用init-method
属性来指定具有void无参数签名的方法的名称。 使用Java配置,你使用@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类上的一个publicclose 或shutdown 方法 实现java.lang.AutoCloseable 或java.io.Closeable 因此匹配)。 这个特殊的(推断 )值也可以在<beans> 元素的default-destroy-method 属性上设置,以将这个行为应用到整个bean集合中(参见默认初始化 销毁方法 )注意,这是Java配置的默认行为. |
默认的初始化函数和销毁函数
当你编写不使用Spring特定的InitializeBean
和DisposableBean
回调接口的初始化和销毁方法回调时,你通常使用诸如init()
,initialize()
,dispose()
,等等。理想情况下,此类生命周期回调方法的名称在整个项目中标准化,以便所有开发人员使用相同的方法名称并确保一致性。
你可以配置Spring容器来查找命名初始化,并在每个 bean上销毁回调方法名。这意味着,作为应用程序开发人员,你可以编写应用程序类并使用称为init()的初始化回调,而无需为每个bean定义配置一个init-method ="init"
属性。 Spring IoC容器在创建bean时(并且根据前面描述的标准生命周期回调契约)调用该方法。此功能还对初始化和destroy方法回调强制执行一致的命名约定。
假设你的初始化回调方法名为init()
,而destroy回调方法名为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.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
在顶层<beans/>
元素属性上的default-init-method
属性的存在导致Spring IoC容器识别一个名为init
在 bean中的方法作为初始化方法回调。当bean被创建和组装时,如果bean类有这样的方法,它会在适当的时间被调用。
你可以使用顶层的<bean/>
元素上的default-destroy-method
属性来类似地配置destroy方法回调(在XML中)。
如果现有的bean类已经具有与常规方式不同的回调方法,则可以使用init-method
和destroy-method
属性指定(在XML中)方法名称来覆盖默认值<bean/>
本身。
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
注解的方法 destroy()
由DisposableBean
回调接口定义- 自定义配置的
destroy()
方法
启动和关闭回调
Lifecycle
接口定义了任何具有自己生命周期需求的对象的基本方法(例如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象可以实现该接口。 然后,当ApplicationContext本身接收到开始和停止信号时,例如, 对于在运行时的停止/重新启动情形,它将级联这些调用到在该上下文中定义的所有Lifecycle
实现。 它通过委托给一个LifecycleProcessor
来实现:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor
本身是Lifecycle
接口的扩展。 它还添加了两个其他方法来对正在刷新和关闭的上下文做出反应。
注意,org.springframework.context.Lifecycle 接口定义规则只是明确了启动/停止通知的定义,并不意味着在上下文刷新时自动启动。 考虑实现org.springframework.context.SmartLifecycle 而不是细粒度控制特定bean的自动启动(包括启动阶段)。 此外,请注意,停止通知不能保证在销毁之前:在正常关闭时,所有Lifecycle bean将首先在传播一般销毁回调之前收到停止通知; 然而,在上下文的生命周期中的热刷新或中止的刷新尝试,只有destroy方法将被调用。 |
启动和关闭调用的顺序很重要。 如果任何两个对象之间存在依赖
关系,则依赖方会在依赖启动之后启动,会在依赖停止之前停止。 然而,有时直接依赖性是未知的。 你可能只知道某种类型的对象应该在另一种类型的对象之前开始。 在这些情况下,SmartLifecycle接口定义了另一个选项,即在它的超级接口Phased
上定义的getPhase()
方法。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,具有最低层次的对象首先开始,并且在停止时,遵循相反的顺序。因此,实现SmartLifecycle
并且其getPhase()
方法返回Integer.MIN_VALUE
的对象将首先开始,最后停止。若是返回了Integer.MAX_VALUE
,那么该方法最后启动最先停止(因为该对象依赖其他bean才能运行)。关于phase
的值,常规的并未实现SmartLifecycle
接口的Lifecycle
对象,其值默认为0。因此,负phase
值表示要在常规Lifecycle
对象之前启动(在常规Lifecycyle
对象之后停止),使用 正值则恰恰相反。
正如你可以看到由SmartLifecycle定义的stop方法接受一个回调。任何实现必须在该实现的关闭过程完成后调用该回调的run()
方法。这使得能够在必要时实现异步关闭,因为LifecycleProcessor
interfaceDefaultLifecycleProcessor
的默认实现将等待到每个阶段内的对象组的超时值以调用该回调。默认每阶段超时为30秒。你可以通过在上下文中定义名为lifecycleProcessor
的bean来覆盖默认生命周期处理器实例。如果只想修改超时,则定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如上所述,LifecycleProcessor
接口定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关闭进程,就像显式地调用了stop()
一样,但是当上下文关闭时,它会发生。另一方面,刷新
回调启用了SmartLifecycle
bean的另一个功能。当上下文被刷新时(在所有对象被实例化和初始化之后),该回调将被调用,并且在那时,默认生命周期处理器将检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的布尔值。如果true
,那么该对象将在该点开始,而不是等待上下文或其自己的start()
方法的显式调用(这和容器刷新不同,标准的容器实现启动不会自动发生)。 phase
值以及任何depends-on
关系将以与上述相同的方式来确定启动顺序。
在非Web应用程序中正常关闭Spring IoC容器
本节仅适用于非Web应用程序。 Spring的基于Web的ApplicationContext 实现已经有代码在相关Web应用程序关闭时优雅地关闭Spring IoC容器。 |
如果你在非Web应用程序环境中使用Spring的IoC容器; 例如,在富客户端桌面环境中; 你使用JVM注册关闭挂接。 这样做可确保正常关闭并调用单元bean上的相关销毁方法,以便释放所有资源。 当然,你仍然必须正确配置和实现这些destroy回调。
要注册一个 shutdown hook,你需要调用在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(
new String []{"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...
}
}