3.6 自定义bean的性质
3.6.1 生命周期回调
要与容器对bean生命周期的管理进行交互,您可以实现Spring的InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用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托管对象还可以实现生命周期接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
本节将描述生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean接口允许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 {
public void afterPropertiesSet() {
// do some initialization work
}
}
但不耦合代码到Spring。
销毁回调
实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。DisposableBean接口指定了一个方法:
void destroy() throws Exception;
建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注解或指定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 {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是并没有耦合到Spring代码里。
注意:元素的destroy-method属性可以分配一个特殊的(推断的)值,该值指示Spring自动检测特定bean类(任何实现java.lang.AutoCloseable和java.io.Closeable的类)上的公共close或shutdown方法。还可以在元素的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.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级元素属性上的default-init-method属性的存在使Spring IoC容器将bean上的init方法识别为初始化方法回调。当创建和组装bean时,如果bean类有这样的方法,则在适当的时候调用它。
通过在顶层元素上使用default-destroy-method属性,可以类似地配置destroy方法回调(即在XML中)。
现有的bean类已经有回调方法,它们的命名与约定不一致,您可以使用本身的init-method和destroy-method属性指定方法名(即在XML中),从而覆盖默认值。
Spring容器保证在向bean提供所有依赖项后立即调用已配置的初始化回调。因此对原始bean引用调用初始化回调,这意味着AOP拦截器等还没有应用到bean。首先完全创建目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是单独定义的,您的代码甚至可以绕过代理与原始目标bean交互。因此,将拦截器应用于init方法将是不一致的,因为这样做将把目标bean的生命周期与其代理/拦截器耦合起来,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5开始,您有三个控制bean生命周期行为的选项:InitializingBean和DisposableBean回调接口;自定义init()和destroy()方法;以及@PostConstruct和@PreDestroy注j解。您可以组合这些机制来控制给定的bean。
注意:如果为bean配置了多个生命周期机制,并且每个机制都配置了一个不同的方法名,那么按照下面列出的顺序执行每个配置的方法。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名(例如,初始化方法的init()),则该方法将执行一次,如上一节所述。
使用不同的初始化方法为同一个bean配置多个生命周期机制,调用如下:
- 使用@PostConstruct注解的方法
- 由InitializingBean回调接口定义的afterPropertiesSet()
- 自定义配置的init()方法
销毁方法的调用顺序相同:
- 使用@PreDestroy注解的方法
- 由DisposableBean回调接口定义的destory ()
- 自定义配置的destroy()方法
启动和关闭回调
生命周期接口定义了任何对象的基本方法,它有自己的生命周期需求(例如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何spring托管对象都可以实现该接口。然后,当ApplicationContext本身接收到启动和停止信号时,例如,对于运行时的停止/重启场景,它将把这些调用级联到在该上下文中定义的所有生命周期实现。它通过委托给一个生命周期处理器来做到这一点:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两种方法来响应正在刷新和关闭的上下文。
注意,常规的org.springframework.context.Lifecycle接口只是显式启动/停止通知的普通契约,并不意味着在上下文刷新时自动启动。考虑实施org.springframework.context.SmartLifecycle用于对特定bean的自动启动(包括启动阶段)进行细粒度控制。此外,请注意,停止通知不能保证在销毁之前出现:在常规关闭时,所有Lifecycel beans将在传播常规销毁回调之前首先收到一个停止通知;然而,在上下文生存期内的热刷新或中止的刷新尝试时,只调用destroy方法。
启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖关系”,依赖方将在依赖项之后启动,并在依赖项之前停止。然而,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超接口上定义的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将指示对象应该最后启动,然后首先停止(可能是因为它取决于要运行的其他进程)。在考虑阶段值时,同样重要的是要知道,对于任何没有实现SmartLifecycle的“正常”生命周期对象,默认阶段都是0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(在它们之后停止),反之亦然。
正如您所看到的,由SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这在必要时启用异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将等待到每个阶段中的对象组调用回调的超时值。每个阶段的默认超时时间为30秒。您可以通过在上下文中定义一个名为“lifecycleProcessor”的bean来覆盖默认的lifecycle processor实例。如果您只想修改超时,那么定义以下内容就足够了:
<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()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。“阶段”值以及任何“依赖”关系将以与上面描述的相同的方式确定启动顺序。
在非web应用程序中优雅地关闭Spring IoC容器
注意:本节只适用于非web应用程序。Spring基于web的ApplicationContext实现已经有了适当的代码,可以在关闭相关web应用程序时优雅地关闭Spring IoC容器。
如果您在非web应用程序环境中使用Spring的IoC容器;例如,在富客户机桌面环境中;您可以向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(
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...
}
}
3.6.2 ApplicationContextAware和BeanNameAware
当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,实例提供了对该应用程序上下文的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过编程方式操纵创建它们的ApplicationContext,通过ApplicationContext接口,或者通过将引用转换为该接口的已知子类,例如ConfigurableApplicationContext,它公开了额外的功能。一种用途是通过编程检索其他bean。有时这种能力是有用的;但是,一般来说,您应该避免使用它,因为它将代码与Spring耦合在一起,并且不遵循控制反转样式(将协作者作为属性提供给bean)。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问MessageSource。这些附加功能在3.15节“ApplicationContext的附加功能”中进行了描述。
从Spring 2.5开始,自动装配是获取对ApplicationContext引用的另一种选择。“传统”constructor和byType自动装配模式(如3.4.5节“自动装配协作者”中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖关系。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用新的基于注释的自动装配特性。如果您这样做了,那么ApplicationContext将被自动拖放到一个字段、构造函数参数或方法参数中,如果该字段、构造函数或方法携带@Autowired注释,则该字段、构造函数或方法将期望得到ApplicationContext类型。有关更多信息,请参见3.9.2节“@Autowired”。
当应用程序上下文创建一个实现org.springframework.beans.factory.BeanNameAware的类时,类提供了对其关联对象定义中定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调函数在填充普通bean属性之后调用,但在初始化回调(如InitializingBean,afterPropertiesSet或自定义init-method)之前调用。
3.6.3 其他Aware接口
除了上面讨论的applicationcontextAware和BeanNameAware, Spring还提供了一系列Aware的接口,允许bean向容器表明它们需要某种基础设施依赖关系。下面总结了最重要的Aware接口——作为一个通用规则,名称很好地指示了依赖类型:
表3.4 Aware接口
名字 | 依赖注入 | 说明 |
---|---|---|
ApplicationContextAware | 声明ApplicationContext | 第3.6.2节,“applicationcontextAware和BeanNameAware” |
ApplicationEventPublisherAware | 封装的ApplicationContext的事件发布程序 | 第3.15节,“ApplicationContext的附加功能” |
ApplicationEventPublisherAware | 类加载器用于加载bean类。 | 第3.3.2节,“实例化bean” |
BeanFactoryAware | 声明BeanFactory | 第3.6.2节,“applicationcontextAware和BeanNameAware” |
BeanNameAware | 声明bean的名称 | 第3.6.2节,“applicationcontextAware和BeanNameAware” |
BootstrapContextAware | 资源适配器引导容器运行的上下文。通常只在JCA感知的应用程序上下文中可用 | 第28章,JCA CCI |
LoadTimeWeaverAware | 定义了用于在加载时处理类定义的weaver | 第7.8.4节,“在Spring框架中使用AspectJ进行加载时编织” |
MessageSourceAware | 已配置的消息解析策略(支持参数化和国际化) | 第3.15节,“ApplicationContext的附加功能” |
NotificationPublisherAware | Spring JMX通知发布程序 | 27.7节,“通知” |
ResourceLoaderAware | 配置的加载程序,用于低层访问资源 | 第四章、资源 |
ServletConfigAware | 容器运行在当前ServletContext中。仅在web-aware的Spring应用程序上下文中有效 | 第18章,Web MVC框架 |
ServletContextAware | 容器运行在当前ServletContext中。仅在web-aware的Spring应用程序上下文中有效 | 第18章,Web MVC框架 |
再次注意,这些接口的使用将您的代码绑定到Spring API,并且不遵循控制反转样式。因此,建议将它们用于需要对容器进行编程访问的基础设施bean。