为了与容器对 bean 生命周期的管理进行交互,可以实现 Spring InitializingBean
和 DisposableBean
接口。容器为前者调用 afterPropertieSet
,为后者调用 destroy()
,以便 bean 在初始化和销毁 bean 时执行某些操作。
:::info JSR-250 @PostConstruct 和 @PreDestroy 注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的 bean 没有耦合到特定于 Spring 的接口。有关详细信息,请参阅使用@PostConstruct 和 @PreDestroy。
如果不想使用 JSR-250 注解,但仍想删除耦合,请考虑 init-method 和 destroy-method 定义 bean 元数据。 :::
在内部,Spring 框架使用 BeanPostProcessor 实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要定制功能或 Spring 默认不提供的其他生命周期行为,您可以自己实现 BeanPostProcessor。有关详细信息,请参见 容器扩展点。
除了初始化和销毁回调之外,Spring 管理的对象还可以实现生命周期 Lifecycle 接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
初始化回调
org.springframework.Beans.Factory.InitializationBean
接口允许 Bean 在容器中设置 Bean 上的 所有必需属性 后执行初始化工作。 initializingBean 接口指定单个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用 InitializingBean 接口,因为它不必要地 将代码耦合到 Spring。或者,我们建议使用@PostConstruct
注解或指定 POJO 初始化方法。对于基于 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。
销毁回调
实现 org.springframework.beans.factory.DisposableBean
接口允许 bean 在包含它的容器被销毁时获得回调(容器将它移除时,容器在销毁时,也会将所有的 bean 移除)。DisposableBean 接口指定了一种方法:
void destroy() throws Exception;
XML 可以使用 destroy-method 属性
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
或则实现 DisposableBean 接口
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
或则使用注解 @PreDestroy
public class ExampleBean {
@PreDestroy
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
默认初始化和销毁方法
当编写不使用 Spring 特定的 InitializingBean 和 DisposableBean 回调接口的初始化和销毁方法回调时,通常会编写名为 init()
, initialize()
, dispose()
等的方法。理想情况下,这样的生命周期回调方法的名称在整个项目中都是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。
简单说,统一这些生命周期方法之后,可以全局配置,所有具有这些方法名称的 bean 都可以在合适的时机回调这些方法。或则你也可以自己写程序从容器中获取到所有的类来手动调用
如下面的类可以配置全局的 初始化回调
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
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
还可以配置 default-destroy-method
;
如果 bean 元素上已经配置过了 init-method 方法,那么就会忽略 default-init-method 的配置。
Spring 容器保证在为 bean 提供所有依赖项后立即调用已配置的初始化回调。因此,对原始 bean 引用调用初始化回调,这意味着 AOP 拦截器等尚未应用于 bean。首先完全创建一个目标 bean,然后应用一个AOP 代理(例如)及其拦截器链。如果目标 bean 和代理是单独定义的,那么代码甚至可以绕过代理与原始目标 bean 交互。因此,将拦截器应用于 init 方法是不一致的,因为这样做会将目标 bean 的生命周期与其代理拦截器耦合,并在代码直接与原始目标 bean 交互时留下奇怪的语义。
组合生命周期机制
在 Spring 2.5中,控制 bean 生命周期行为有三个选项:
- 实现 InitializingBean 和 DisposableBean 接口
- 自定义
init()
和destroy()
方法;就是 XML 中指定的方式 - 使用注解
@PostConstruct
、@PreDestroy
如果上面 3 个机制都实现了,并且他们的方法名称都不一样(一样的会只会被调用一次)他们的调用顺序如下:
- 注解:
@PostConstruct
、@PreDestroy
- 实现接口:
InitializingBean.afterPropertiesSet()
、DisposableBean .destroy()
- XML 中指定的方法
启动和关闭回调
Lifecycle
接口定义了任何具有自身生命周期需求的对象的基本方法(例如启动和停止某些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring 管理的对象都可以实现生命周期接口。然后,当 ApplicationContext 本身接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它会将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托给 LifecycleProcessor 来实现这一点,如下清单所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor 本身就是 Lifecycle 接口的一个扩展。它还添加了另外两个方法,用于对刷新和关闭的上下文作出反应。
:::info
请注意:常规的 org.springframework.context.Lifecycle 是显式启动和停止通知的简单约定,并不意味着在上下文刷新时自动启动。对于细粒度控制特定 bean 的自动启动(包括启动阶段),考虑实现
org.springframework.context.SmartLifecycle
此外,请注意,停止通知不保证在销毁前发出。在定期关闭时,所有生命周期 bean 在传播常规销毁回调之前首先收到停止通知。但是,在上下文的生命周期内进行热刷新或停止刷新尝试时,只调用destroy 方法。 :::
启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在「依赖」关系,则依赖方在其依赖项之后开始,在其依赖项之前停止。然而,有时,直接依赖关系是未知的。您可能只知道某一类型的对象应该在另一类型的对象之前启动。在这些情况下,SmartLifecycle 接口定义了另一个选项,即其超级接口上定义的 getPhase()
方法。下表显示了分阶段接口的定义:
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_VALU 值将指示对象应该最后启动,然后首先停止(可能是因为它取决于要运行的其他进程)。在考虑阶段值时,还必须知道,任何「正常的」生命周期对象的默认阶段,没有实现 SmartLifecycle
接口时,他的默认值是 0 。因此,任何负数值都表示对象应在这些标准组件之前启动(并在它们之后停止),大于 0 这相反。(这里需要注意:实现 SmartLifecycle
接口,phase 值默认是 Integer.MAX_VALU,而不实现这个接口的对象,它实际上是没有 phase 的概念的,但是生命周期阶段来说,它的默认值是 0)
如果两个对象都实现了 SmartLifecycle 接口,getPhase()
一个返回 1 , 一个返回 2,在程序启动后,返回 1 的那个 start 方法会被先调用;
区别:
- Lifecycle:只有容器在启动或则销毁时,才会触发 start 和 stop 方法,并且没有分阶段启动销毁的功能
- SmartLifecycle:支持在容器刷新的时候,也可以触发 start 和 stop 方法,并且支持分阶段启动和销毁
SmartLifecycle 定义的 stop 方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调函数的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 beans 的另一项功能。刷新上下文时(在所有对象都被实例化和初始化之后),将调用该回调。此时,默认生命周期处理器将检查每个 SmartLifecycle 对象的 isAutoStartup()
方法返回的布尔值。如果为true,则该对象将在该点启动,而不是等待显式调用上下文或其自身的 start
方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,phase 值 和任何「依赖」关系决定启动顺序。
简单来说:对于启动和关闭的功能可以用下面的例子来看
package cn.mrcode.study.springdocsread.web;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
/**
* @author mrcode
*/
@Component
public class ExampleBean implements SmartLifecycle {
private boolean isRunning;
@Override
public void start() {
System.out.println("start");
}
@Override
public void stop() {
System.out.println("stop");
}
@Override
public boolean isRunning() {
System.out.println("isRunning");
boolean temp = isRunning;
isRunning = true;
return temp;
}
@Override
public int getPhase() {
return -1;
}
}
是否调用 start 方法和 stop 方法,是 isRunning 控制的,
- 如果在容器启动时,返回 true 表示该组件已经启动,就不会调用 start 方法了。
- 同样的,如果在容器关闭时,返回 true 就会调用 stop 方法,如果返回 false,就不会调用 stop 方法
在非 web 应用程序中优雅地关闭 Spring IoC 容器
:::tips 本节仅适用于非 web 应用程序。Spring 的基于 web 的 ApplicationContext 实现已经准备好了代码,可以在相关 web 应用程序关闭时优雅地关闭 Spring IoC 容器。 :::
如果在非 web 应用程序环境中(例如,在富客户端桌面环境中)使用 Spring 的 IoC 容器,请向 JVM 注册一个 shutdown hook。这样做可以确保优雅地关闭并调用单例 bean 上的相关 destroy 方法,从而释放所有资源。您仍然必须正确地配置和实现这些销毁回调。
要注册一个 shutdown hook,请调用 ConfigurableApplicationContext 接口上声明的 registershutdown 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");
// 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...
}
}
总结
- 首先 bean 有生命周期,被初始化和被容器移除都有相关的生命周期事件
- 容器启动和刷新、关闭也有相关的生命周期事件
所以在之前笔者疑惑的一些自定义的死循环中处理一些业务逻辑的场景下,就要实现下 Lifecycle 接口,当容器被关闭时(一般是应用程序要关闭)就可以在 stop 里面将死循环的逻辑跳出。