文章作者:Tyan
博客:noahsnail.com | CSDN |
3.6 定制bean特性
3.6.1 生命周期回调
为了与容器中bean生命周期的管理进行交互,你可以实现Spring的InitializingBean
和DisposableBean
接口。当初始化beans时容器会调用InitializingBean
中的afterPropertiesSet()
方法,当销毁beans时容器会调用DisposableBean
中的destroy()
方法,在这两个方法中bean可以执行特定的行为。
在现代Spring应用中,通常认为JSR-250的
@PostConstruct
和@PreDestroy
注解是最佳实践接收生命周期回调函数的方法。使用这些注解意味着你的bean没有耦合Spring特定的接口。更多细节请看3.9.8小节,"@PostConstruct和@PreDestroy"。
如果你不想使用JSR-250注解,但你仍要注意解耦,可以考虑使用对象定义元数据中的初始化方法和销毁方法。
在Spring内部,Spring框架使用BeanPostProcessor
实现来处理任何它能发现的回调接口并调用合适的方法。如果你需要定制Spring不能提供的开箱即用的功能或其它生命周期行为,你可以自己实现BeanPostProcessor
。更多信息请看3.8小节,"容器扩展点"。
除了初始化回调函数和销毁回调函数之外,Spring管理的对象也可以实现Lifecycle
接口,这些对象可以参与容器自身生命周期驱动的启动和关闭过程。
本节描述了生命周期回调接口。
初始化回调函数
org.springframework.beans.factory.InitializingBean
接口在容器设置了bean所有的必须属性之后,允许bean执行初始化工作。InitializingBean
接口指定了一个方法:
void afterPropertiesSet() throws Exception;
建议你不使用InitializingBean
接口,因为它对代码与Spring进行了不必要的耦合。作为一种替代方法,你可以使用@PostConstruct
注解或指定一个POPJO的初始化方法。在基于XML配置元数据的情况下,你可以使用init-method
特性来指定方法的名称,方法是没有返回值和参数的。如果使用Java配置,你可以使用@Bean
的initMethod
特性,请看"接收生命周期回调函数"小节。例如,下面的代码:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
等价于:
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
特性,请看"接收生命周期回调"小节。例如,下面的定义:
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
等价于:
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但没有与Spring代码耦合。
默认初始化和销毁方法
当你编写初始化回调函数和析构回调函数时,不要使用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.");
}
}
}
位于顶层
元素中的default-init-method
特性,会让Spring IoC容器将beans中的名为init
的方法识别为初始化回调方法。当一个bean创建和组装时,如果bean类有这样一个方法,它会在恰当的时间被调用。
在bean被提供了所有依赖之后,Spring容器确保会立刻调用配置的初始化回调方法。因此初始化回调会在原生bean引用上调用,这意味着AOP拦截器等仍不能应用到bean中。首先要完整的创建目标bean,然后才会应用AOP代理(例如)等拦截器链。如果分别定义了目标bean和代理,你的代码甚至能绕过代理直接与原生的目标bean进行交互。将拦截器应用到初始化方法上可能会产生不一致性,因为这样做会使目标bean的生命周期与它的代理/拦截器相耦合,当你的代码与原生目标bean直接进行交互时,语义会变的很奇怪。
组合生命周期机制
从Spring 2.5开始,在控制bean的生命周期行为时,你有三中选择:InitializingBean和
DisposableBean回调接口;定制
init()和
destroy()方法;
@PostConstruct和
@PreDestroy`注解。在控制一个给定bean时你可以组合这些机制。
如果一个bean配置了多生命周期机制,每种机制配置了一个不同的方法名,那么每一个配置的方法会按照下面的顺序列表来执行。但是如果配置了相同的名字——例如,
init()
初始化方法——不止在一个生命周期机制中配置,那么这个方法只能执行一次,像之前所说的那样。
同一个bean配置了多生命周期机制,并有不同的初始化方法,那么调用顺序如下:
先调用有注解
@PostConstruct
的方法然后调用
InitializingBean
回调接口定义的afterPropertiesSet()
方法最好调用定制配置的
init()
方法
Destroy methods are called in the same order:
Methods annotated with
@PreDestroy
destroy()
as defined by theDisposableBean
callback interfaceA custom configured
destroy()
method
析构方法按同样的顺序调用:
先调用有
@PreDestroy
注解的方法再调用
DisposableBean
回调接口定义的destroy()
方法最好调用定制配置的
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
beans在通常的析构回调传播之前首先会收到停止通知;但是,在上下文使用期间进行热刷新或尝试取消再刷新,只会调用析构方法。
启动和关闭的调用顺序是很重要的。如果任何两个对象间存在一个"depends-on"关系,那么依赖关系将在它的依赖之后开始,在它的依赖之前停止。然而有时直接的依赖关系是未知的。你可能只知道某个类型的对象应该在另一个类型的对象之前启动。在那种情况下,SmartLifecycle
接口定义了另一种选择,也就是说getPhase()
定义在它的父接口Phased
中。
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
接口的Lifecycle
对象的默认值为0是很重要的。因此,任何负相位值表示对象应该在那么标准组件之前启动(在它们之后停止),反之为任何正相位值。
正如你看到的,在SmartLifecycle
中定义的停止方法接收一个回调函数。任何实现在关闭进程完成之后都必须调用回调的run()
方法。当需要时这可以进行异步关闭,因为LifecycleProcessor
接口、DefaultLifecycleProcessor
接口的默认实现会等待每个阶段的对象组直到达到超时值,然后调用回调函数。默认每个阶段的超时值为30秒。你可以在上下文中通过定义名为"lifecycleProcessor"的bean来覆盖默认的生命周期处理器实例。如果你只想修改超时值,如下定义是足够的:
像上面提到的那样,LifecycleProcessor
接口为再刷新和上下文的关闭也定义了回调方法。后者会简单的驱动关闭进程就像显式的调用了stop()
方法一样,但当上下文关闭时它才会发生。另一方面refresh
回调能使SmartLifecycle
beans的另一个功能可用。当上下文再刷新时(所有对象已经实例化并初始化),回调函数将被调用,那时默认的生命周期处理器将会检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的布尔值。如果为true
,对象将会在那时启动而不是等待上下文的显式调用或它自己的start()
方法(不像上下文再刷新,对于一个标准的上下文实现上下启动不会自动发生)。"phase"值以及"depends-on"关系将决定启动顺序,像上面描述的一样。
在非web应用中妥善的关闭Spring IoC容器
这一节只应用于非web应用。Spring的基于web的
ApplicationContext
实现已经有代码来处理当相关的web应用关闭时,妥善关闭Spring IoC容器的问题。
如果你在非web应用环境使用Spring的IoC容器;例如,在一个富桌面客户端环境中,你在JVM中注册一个关闭钩子。这样做确保了妥善的关闭,为了释放所有资源需要调用与单例beans相关的析构方法。当然,你仍然必须正确的配置和实现这些销毁回调函数。
为了注册一个关闭钩子,你可以调用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...
}
}
3.6.2 ApplicationContextAware和BeanNameAware
当ApplicationContext
创建一个实现org.springframework.context.ApplicationContextAware
接口的对象实例时,这个实例会提供一个ApplicationContext
的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此beans可以以编程方式操纵创建它们的ApplicationContext
,通过ApplicationContext
接口,或通过将引用抛给这个接口的一个已知子类,例如ConfigurableApplicationContext
,它暴露了额外的功能。一个方法是编程式检索其他的bean。有时这个能力是很有用的,但是通常你应该避免使用它,因为它耦合了代码和Spring,不能遵循控制反转的风格,在控制反转中协作者是作为属性提供给beans的。ApplicationContext
的其它方法提供了对文件资源的访问,发布应用事件,访问MessageSource
的功能。这些额外的特性将在3.15小节『ApplicationContext”的额外能力』中描述。
从Spring 2.5起,自动装配是另一种可替代的获得ApplicationContext
引用的方法。『传统的』constructor
和byType
自动装配模式(如3.4.5小节所述,『自动装配协作者』)可以分别为构造函数参数或setter方法参数提供ApplicationContext
类型的依赖。更多的灵活性包括自动装配变量的能力和多参数方法,使用新的基于注解的自动装配特性。如果你这一做的话,ApplicationContext
可以被自动装配到变量中,构造函数参数中或方法参数中,如果讨论的变量,构造函数或方法有@Autowired
注解,那么可以期望它是ApplicationContext
类型。更多信息请看3.9.2小节,@autowired
。
当ApplicationContext
创建一个实现了org.springframework.beans.factory.BeanNameAware
接口的类时,类中有相关的对象定义中定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在正常的bean属性填入之后,回调方法调用,但在初始化回调方法之前,例如InitializingBean
的afterPropertiesSet或一个定制的初始化方法。
3.6.3 其它的Aware接口
除了上面讨论的ApplicationContextAware
和BeanNameAware
之外,Spring给予了一系列Aware
接口来允许beans向容器表明它们需要一个确定的基础结构依赖。最重要的Aware
接口总结如下——作为一个通用规则,名字是依赖类型的一个很好暗示:
表3.4. Aware接口
Name | Injected Dependency | Explained in |
---|---|---|
ApplicationContextAware | 声明ApplicationContext |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | 封装事件发布的ApplicationContext |
Section 3.15, “Additional Capabilities of the ApplicationContext” |
BeanClassLoaderAware | 用来加载bean的类加载器 | Section 3.3.2, “Instantiating beans” |
BeanFactoryAware | 声明BeanFactory |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BeanNameAware | 声明的bean的名字 | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BootstrapContextAware | 容器运行的资源自适应BootstrapContext . 通常只在JCA aware ApplicationContexts 可获得 |
Chapter 28, JCA CCI |
LoadTimeWeaverAware | 加载时为处理类定义定义的weaver | Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | 解析消息配置策略 (支持参数化和国际化) | Section 3.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX通知发布器 | Section 27.7, “Notifications” |
ResourceLoaderAware | 为底层访问资源配置的加载器 | Chapter 4, Resources |
ServletConfigAware | 容器运行的当前ServletConfig 。 仅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
ServletContextAware | 容器运行的当前ServletContext 。 仅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
注意这些接口的用法将你的代码与Spring进行了捆绑,不符合控制反转的风格。因此,它们是为那么需要以编程方式访问容器的基础结构beans推荐的。