目录
1、初始化回调
2、销毁方法回调
2、默认的初始化和销毁方法
3、生命周期回调方法的执行顺序
4、启动和关闭的回调方法:Lifecycle 接口
5、在非 web 应用程序中优雅地关闭 Spring IoC 容器
Spring 框架提供了许多接口,你可以使用这些接口自定义bean的性质。
实现 InitializingBean 和 DisposableBean 接口与管理 bean 生命周期的容器进行交互。容器可以在 bean 初始化之前调用 afterPropertiesSet() 方法,在 bean 销毁之后调用 destroy() 方法,来执行一些操作。// 第一种方式
使用 Java JSR-250 规范中的 @PostConstruct 和 @PreDestroy 注解通常被认为是 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解,意味着你的 bean 没有与特定的 Spring 接口进行耦合。两个注解的详细信息请参考这里。// 第二种方式
使用 bean 定义中的 init-method 和 destroy-method 属性。// 第三种方式
在 Spring 框架内部,Spring 通过对 BeanPostProcessor 接口的方法实现来处理 bean 的接口回调。如果你需要定制一些 bean 的特性或者添加一些 Spring 没有提供的 bean 生命周期行为,你可以自己实现 BeanPostProcessor 接口,如果想要了解更多信息,请点击这里。// 第四种方式
Lifecycle 接口。所以,这些被 Spring 管理的对象能够参与到容器的启动和关闭的过程中来。// 容器和 Lifecycle 接口
1、初始化回调
接口允许 bean 在 Spring 容器设置了 bean 所有必须属性后,再执行初始化工作。InitializingBean 接口中定义了一个方法
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
Spring 官方并不推荐直接使用 InitializingBean 接口,因为没有必要把代码和 Spring 进行耦合。Spring 推荐使用 Java JSR-250 规范中的 @PostConstruct 注解或者使用 bean 定义中的初始化回调方法。在 bean 的 xml 配置中,你可使用 init-method 属性指定一个无参方法的方法名来实现初始化回调,如果使用的是 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 {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
但是,第一个示例中的代码并没有与 Spring 耦合。
2、销毁方法回调
org.springframework.beans.factory.DisposableBean 接口允许 bean 在容器中被销毁后再进行回调,DisposableBean 接口中定义了一个方法:
public interface DisposableBean {
void destroy() throws Exception;
}
@PreDestroy 注解或者使用 bean 定义中的销毁回调方法。 在 bean 的 xml 配置中,你可使用 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 {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,第一个示例中的代码并没有与 Spring 耦合。
// 默认行为:init() 为初始化回调方法
2、默认的初始化和销毁方法
当你不使用 Spring 的 InitializingBean 和 DisposableBean 回调接口编写 initialization 或 destroy 回调方法时,你通常会使用 init(),initialize(),dispose() 等方法来命名回调接口。但理想情况下,对这些生命周期的回调方法的命名应该进行标准化,从而使得在一个项目中都使用相同的,一致的方法名。
// 统一回调方法命名
建议初始化回调方法使用 init() 命名,销毁回调方法使用 destroy() 命名,就像下边的例子一样:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// 初始化回调方法
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.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
在顶层的 <beans/> 标签中配置 default-init-method 属性,使 Spring 容器能够将 class 中的 init() 的方法识别为初始化回调方法。当一个 bean 被创建后,如果它的 class 中有 init() 方法,Spring 会在恰当的时候自动调用它。定义销毁回调方法跟上边步骤一样。
// 自定义方法命名
当一个 bean 被完全创建后,该 bean 的 AOP 代理的拦截器链才会被执行。如果一个 bean 和它的代理被分开定义,那么你甚至能绕过代理直接与原生 bean 进行交互。因此,Spring 容器的初始化回调跟拦截应用到 init() 方法是不一致的,因为这样做,会把 bean 的生命周期跟它的代理或者拦截器耦合在一起,并在直接与原生 bean 交互时产生奇怪的语义。// 这段翻译得不是很顺,大意就是不要把 bean 中的生命周期方法跟拦截器耦合在一起
3、生命周期回调方法的执行顺序
在 Spring 中,你可以通过以下三点来控制生命周期的行为:
- InitializingBean 和 DisposableBean 回调接口
- 自定义 init() 和 destroy() 方法
- @PostConstruct 和 @PreDestroy 注解
如果为多个生命周期机制配置了相同的方法名称,例如,所有机制的初始化方法都为 init() ,那么该方法只会被执行一次。
为同一个 bean 配置多个生命周期回调机制,且每个机制都配置了不同的方法名的情况下,方法的执行顺序如下:
- 执行 @PostConstruct 注解标记的方法
- 执行 InitializingBean 回调接口中的 afterPropertiesSet() 方法
- 执行自定义的 init() 方法
同样情况下,销毁回调方法的顺序如下:
- 执行 @PreDestroy 注解标记的方法
- 执行 DisposableBean 回调接口中的 destroy() 方法
- 执行自定义的 destroy() 方法
4、启动和关闭的回调方法:Lifecycle 接口
// 定义顺序,很重要的机制
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
当 ApplicationContext 容器接收到启动或者停止信号时,它会调用容器中定义的所有 Lifecycle 接口的实现方法。容器通过 LifecycleProcessor 接口来实现这些调用,LifecycleProcessor 接口如下边所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor 接口是对 Lifecycle 接口的扩展,它额外提供了两个方法来响应上下文的刷新和关闭。
// 在 Spring 中,上下文就是指容器
实现了 Lifecycle 接口的 bean 会在执行销毁回调方法之前收到停止通知。然而,在上下文生命周期中的热刷新或停止刷新尝试时,只会调用 destroy() 方法。// 最后这句难以理解,什么是 Hot refresh?
启动和关闭的调用顺序是非常重要的。如果两个对象之间存在依赖关系,被依赖的 bean 会在依赖此对象的 bean 之前先启动,关闭的顺序正好相反。但是,有时候,你并不能直接知道 Objects 之间的依赖关系。你可能只知道某一类型的对象应该先于另一类型的对象启动。在这种情况下 SmartLifecycle 接口提供了另一个选择:getPhase() 方法,该方法由 SmartLifecycle 的父接口 Phased 定义,下边代码展示了 Phased 接口:
public interface Phased {
int getPhase();
}
下边代码展示了 SmartLifecycle 接口:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,phase 最小的对象先启动。当停止时,遵循相反的顺序。因此,实现 SmartLifecycle 接口的对象,如果它的 getPhase() 返回 Integer.MIN_VALUE(最小值 -2147483648),那么该对象将是第一个启动和最后一个停止的。另一方面,如果 phase 值为 Integer.MAX_VALUE,那么该对象是最后启动并首先停止的。另外,还需要考虑到没有实现 SmartLifecycle 接口而只是实现了 Lifecycle 接口的常规对象,这些对象的 phase 值默认为 0。因此,这意味着 phase 值为负的对象会在常规组件启动之前启动,在常规组件停止后停止,phase 值为正的情况刚好相反。// 非常重要,定义了 bean 之间的启动顺序。有没有顿悟?哈哈哈...
SmartLifecycle 接口中定义的 stop 方法接受回调。任何实现类都必须在该实现类关闭过程完成后,调用该回调的 run() 方法。这在必要的时候能够支持异步关闭,DefaultLifecycleProcessor 是 LifecycleProcessor 接口的默认实现 ,它会在每个阶段都等待一个对象的超时值,然后再去调用回调方法。每个阶段的超时值默认为 30 秒。当然,你可以自定义生命周期处理器,只要在上下文中定义一个名为 lifecycleProcessor 的 bean 就可以了,如果你只想修改超时时间,那么,下边的代码就足够了: // 自定义生命周期处理器,修改对象回调的超时时间
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
LifecycleProcessor 接口还提供了刷新(onRefresh)和关闭容器(onClose)的回调方法。调用 onClose() 时,就像显式调用了 stop() 一样,但是它发生在容器关闭的过程中。另一方面,'refresh' 回调开启了 SmartLifecycle bean 的另一个特性。当刷新容器时(此时,所有对象都已实例化和初始化),将调用 onRefresh 回调方法。在这个时间点上,默认的生命周期处理器会检查每个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值,如果为 true,该对象将在此时启动,而不是等待容器或自己的 start() 方法的显示调用(与刷新容器不同,对于标准容器实现,启动容器不会自动发生)。如前面所述,phase 值和依赖关系(“depends-on” relationships)决定对象的启动顺序。// 刷新容器时调用 onRefresh 方法,容器不会自动启动,但是会自动刷新
5、在非 web 应用程序中优雅地关闭 Spring IoC 容器
基于 web 的 Spring ApplicationContext 容器,在关闭 web 应用程序时,已经有了优雅关闭 Spring IoC 容器的实现。
向 JVM 注册一个关闭的钩子(shutdown hook)。这样做,可以确保容器被优雅的关闭,并且能够通过调用单例 bean 上相关的 destroy 方法,释放所有的资源。但是,你必须要正确地配置和实现这些销毁回调方法。// 注册关闭钩子 shutdown hook
调用 ConfigurableApplicationContext 接口当中的 registerShutdownHook() 方法,可以注册一个 shutdown hook,代码如下所示:
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");
// 为上面的容器添加一个shutdown钩子...
ctx.registerShutdownHook();
// app runs here...
// Main方法退出,钩子在应用程序关闭之前被调用…
}
}
至此,如何在非 web 应用程序中优雅地关闭 Spring IoC 容器的方法介绍完毕。