1.6 自定义bean的性质
Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将其分组如下:
- 生命周期
- ApplicationContextAware和 BeanNameAware
- 其它Aware接口
1.6.1. 生命周期
要与容器对bean生命周期的管理进行交互,可以实现Spring的InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet() ,为后者调用destroy() ,以便bean在初始化和销毁bean时执行某些操作。
JSR-250的@PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定的Spring的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。
如果您不想使用JSR-250注释,但仍然想删除耦合,那么考虑在配置里定义init方法并销毁方法的bean。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要定制特性或其他生命周期行为,那么Spring在默认情况下不提供,您可以自己实现beanPostprocessor。
除了初始化和销毁回调之外,Spring管理的对象还可以实现生命周期接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
生命周期回调接口在本节中进行了描述。
初始化
org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean的所有必要属性之后执行初始化工作。InitializingBean接口指定一个方法:
void afterPropertiesSet() throws Exception;
我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到spring。或者,我们建议使用InitializingBean注释或指定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的estroyMethod属性,请参见接收生命周期回调。考虑以下定义:
<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类上的公共关闭或关闭方法。(因此,实现java.lang.AutoCloseable
或java.io.Closeable
的任何类都将起作用。)您还可以在<beans>
元素的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.");
}
}
}
然后可以在类似于以下内容的bean中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级<beans/>
元素属性上存在default-init-method属性会导致spring ioc容器将bean类上名为init的方法识别为初始化方法。在创建和组装bean时,如果bean类具有这样的方法,则会在适当的时间调用它。
通过在顶层的<beans/>
元素上使用default-destroy-method 属性,可以类似地配置destroy方法回调。
如果现有bean类已经具有与约定不一致的回调方法,你可以重写,并在<bean/>
的 init-method 和 destroy-method 属性来指定方法。
Spring容器保证在向bean提供所有依赖项之后立即调用配置的初始化方法。因此,对原始bean调用初始化,这意味着AOP拦截器等尚未应用于bean。首先完全创建一个目标bean,然后应用一个带有拦截器链的AOP代理。如果目标bean和代理是单独定义的,那么您的代码甚至可以与原始目标bean交互,而不必使用代理。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring2.5开始,您有三个控制bean生命周期行为的选项:
- InitializingBean 和 DisposableBean调用接口
- 自定义init()和destroy()方法
- @PostConstruct 和 @PreDestroy注解,您可以组合这些机制来控制给定的bean。
如果使用多种机制,配置了不同方法名,则按顺序执行;如果相同,则只执行一次
为同一个bean配置的具有不同初始化方法的多个机制如下所示:
- 1.使用@PostConstruct
- InitializingBean中定义afterPropertiesSet()方法
- 3.自定义的init()方法
按相同顺序调用destroy方法:
- 1.用@PreDestroy注释的方法
- 2.由DisposableBean接口定义destroy()方法
- 3.自定义destroy()方法
启动和关闭调用
Lifecycle接口为任何有自己生命周期需求的对象定义了基本方法(例如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收到启动和停止信号(例如,对于运行时的停止/重新启动场景)时,它将这些调用级联到该上下文中定义的所有Lifecycle实现类。它通过委托给LifecycleProcessor来实现这一点,如下列表所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor本身就是生命周期接口的扩展。它还添加了另外两种方法来响应正在刷新和关闭的上下文。
请注意,常规的org.springframework.context.Lifecycle 接口是显式启动和停止通知的简单契约,并不意味着在上下文刷新时自动启动。对于特定bean自动启动的细粒度控制(包括启动阶段),考虑改为实现org.springframework.context.SmartLifecycle。
另外,请注意,停止通知不保证在销毁之前发出。在常规关闭时,所有 Lifecycle 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_VALUE的对象应在最后启动并最先停止(可能是因为它取决于要运行的其他进程)。在考虑相位值时,还必须知道不实现SmartLifecycle的任何“正常”生命周期对象的默认阶段是0。因此,任何负相位值都表示一个对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,则相反。
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>
“刷新”回调方法启用了SmartLifecycleBean的另一个功能。刷新上下文时(在所有对象都已实例化和初始化之后),将调用该回调方法。此时,默认的生命周期处理器检查每个SmartLifecycle对象的isautoStartup()方法的返回值。如果为true,则该对象将在该点启动,而不是等待显式调用上下文或其自身的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,启动顺序由相位值和任何“依赖”关系决定。
在非Web应用程序中优雅地关闭SpringIOC容器
本节仅适用于非Web应用程序。基于Web的ApplicationContext实现已经有了适当的代码,可以在相关Web应用程序关闭时优雅地关闭SpringIOC容器。
如果您在非Web应用程序环境(例如,在富客户机桌面环境中)中使用Spring的IOC容器,请在JVM中注册一个关闭挂钩。这样做可以确保正常关闭,并在单例bean上调用相关的销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。
要注册关闭挂钩,请调用在ConfigurableApplicationContext 接口上声明的registerShutdownBook()方法,如下示例所示:
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...
// 主方法退出,在应用程序关闭之前调用hook…
}
}
1.6.2. ApplicationContextAware和BeanNameAware
当ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,将向该实例提供对该ApplicationContext的引用。以下列表显示了ApplicationContextAware接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口或通过将它的引用强制转换为此接口的已知子类(如ConfigurableApplicationContext,它公开了其他功能),以编程方式操作创建它们的ApplicationContext。一种用途是对其他bean进行编程检索,有时这种能力是有用的。但是,一般来说,您应该避免使用它,因为它将代码与Spring耦合起来,并且不遵循控制反转的样式,协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和对MessageSource的访问。
从Spring2.5开始,autowiring是另一个获得对ApplicationContext引用的替代方法。“传统”构造函数和byType自动装载可以分别为构造函数参数或setter方法参数提供ApplicationContext的依赖关系。为了获得更大的灵活性,包括自动装载字段和多个参数方法的能力,请使用新的基于注释的自动连接功能。如果执行此操作,则ApplicationContext将自动装载到需要ApplicationContext类型的字段、构造函数参数或方法参数中,前提是相关字段、构造函数或方法带有@Autowired注释。
当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,将向提供名称的引用。下面的列表显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在填充普通bean属性之后,但在初始化方法(如initializingbean、afterpropertiesset或自定义init方法)之前调用这个方法。
1.6.3. 其它Aware接口
除了ApplicationContextAware和BeanNameAware(前面讨论过)之外,Spring还提供了一系列Aware接口,给bean提供某种基础结构依赖性。作为一般规则,名称指明依赖项类型。下表总结了最重要的Aware感知接口:
表4.Aware interfaces
名称 | 注入依赖项 |
---|---|
ApplicationContextAware | 声明ApplicationContext。 |
ApplicationEventPublisherAware | 封闭ApplicationContext的事件发布者。 |
BeanClassLoaderAware | 用于加载bean类的类装入器 |
BeanFactoryAware | 声明BeanFactory |
BeanNameAware | 声明bean的名称。 |
BootstrapContextAware | 容器运行的资源适配器BootstrapContext 。通常仅在支持JCA的应用程序上下文实例中可用。 |
LoadTimeWeaverAware | 用于在加载时处理类定义的定义的weaver。 |
MessageSourceAware | 配置的消息解析策略(支持参数化和国际化)。 |
NotificationPublisherAware | Spring JMX通知发布服务器。 |
ResourceLoaderAware | 配置的加载程序,用于低级别访问资源。 |
ServletConfigAware | 容器运行的当前ServletConfig。仅在Web应用程序上下文中有效。 |
ervletContextAware | 容器运行的当前servletContext。仅在Web应用程序上下文中有效。 |
请再次注意,使用这些接口将代码与Spring API绑定在一起,并且不遵循控制反转的样式。因此,我们建议将它们用于需要对容器进行编程访问的基础架构bean。