1.6 Customizing the Nature of a Bean(定制bean的性质)
Spring框架提供了许多接口,您可以使用这些接口定制bean的性质。本节将它们分组如下:
- 生命周期回调
- ApplicationContextAware和BeanNameAware
- 其他感知接口
1.6.1. 生命周期回调
为了与容器对bean生命周期的管理进行交互,您可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便在初始化和销毁bean时让bean执行某些操作。
JSR-250 @PostConstruct和@PreDestroy注释通常被认为是现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到spring特定的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。
如果不想使用JSR-250注释,但仍然希望消除耦合,可以考虑init-method和destroy-method bean定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到并调用适当方法的任何回调接口。如果您需要自定义特性或Spring默认情况下没有提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点。
除了初始化和销毁回调外,spring管理的对象还可以实现Lifecycle接口,这样这些对象就可以参与启动和关闭过程,这是由容器自己的生命周期驱动的。
生命周期回调接口将在本节中描述。
Initialization回调
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 {
@Override
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的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。
您可以为
<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"属性。在创建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.");
}
}
}
然后你可以像下面这样在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属性,可以类似地(即在XML中)配置销毁方法回调。
如果现有bean类已经具有与约定不一致的命名的回调方法,您可以通过使用<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注释。您可以组合这些机制来控制给定的bean。
如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法将按照后面列出的顺序运行。但是,如果为多个生命周期机制配置了相同的方法名(例如,初始化方法为init()),则该方法将运行一次,如上一节所述。
为同一个bean配置的多个生命周期机制,使用不同的初始化方法,调用如下:
- 用@PostConstruct注释的方法
- 由InitializingBean回调接口定义的afterPropertiesSet()
- 自定义配置的init()方法
Destroy方法的调用顺序相同:
- 用@PreDestroy注释的方法
- destroy()由DisposableBean回调接口定义
- 自定义配置destroy()方法
启动和关闭回调
生命周期接口为任何有自己生命周期需求的对象定义了基本方法(比如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何spring管理的对象都可以实现Lifecycle接口。然后,当ApplicationContext本身接收到启动和停止信号(例如,对于运行时的停止/重新启动场景)时,它将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托一个LifecycleProcessor来实现这一点,如下表所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor本身就是Lifecycle接口的扩展。它还添加了另外两个方法,用于对刷新和关闭上下文做出反应。
注意,常规的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 {
int DEFAULT_PHASE = Integer.MAX_VALUE;
default boolean isAutoStartup() {
return true;
}
default void stop(Runnable callback) {
this.stop();
callback.run();
}
default int getPhase() {
return Integer.MAX_VALUE;
}
}
启动时,相位最低的物体先启动。当停止时,遵循相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回Integer的对象。MIN_VALUE将是最先启动和最后停止的。在频谱的另一端,整数的相位值。MAX_VALUE将指示对象应该最后启动并首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道任何“正常”生命周期对象没有实现SmartLifecycle的默认阶段是0。因此,任何负相位值都表明对象应该在这些标准组件之前开始(并在它们之后停止)。相反,任何正相位值都是正确的。
SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这允许在必要时异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor等待每个阶段内的对象组的超时值来调用该回调。默认的每阶段超时时间为30秒。您可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的生命周期处理器实例。如果你只想修改超时时间,定义如下就足够了:
public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile long timeoutPerShutdownPhase = 30000L;
//..........
}
<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()方法返回的布尔值。如果为真,则该对象在该点启动,而不是等待显式调用上下文的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,阶段值和任何“依赖”关系决定启动顺序。
在非web应用程序中优雅地关闭Spring IoC容器
本节仅适用于非web应用。Spring基于web的ApplicationContext实现已经有适当的代码,可以在相关的web应用程序关闭时优雅地关闭Spring IoC容器。
如果你在非web应用程序环境中使用Spring的IoC容器(例如,在富客户端桌面环境中),请向JVM注册一个关机钩子。这样做可以确保正常关闭,并调用单例bean上的相关destroy方法,以便释放所有资源。您仍然必须正确配置和实现这些destroy回调函数。
要注册一个关机钩子,调用在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("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...
}
}
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。这些附加特性在ApplicationContext的附加功能中进行了描述。
Autowiring是获取ApplicationContext引用的另一种选择。传统的构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供类型为ApplicationContext的依赖项。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,请使用基于注释的自动装配特性。如果这样做,则ApplicationContext将自动连接到一个字段、构造函数参数或方法参数中,如果所讨论的字段、构造函数或方法携带@Autowired注释,则这些字段、构造函数参数或方法参数将期望ApplicationContext类型。有关更多信息,请参见使用@Autowired。
当ApplicationContext创建实现org.springframework.bean .factory. beannameaware接口的类时,该类将提供对其关联对象定义中定义的名称的引用。下面的清单显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
该回调在填充普通bean属性之后调用,但在初始化回调(如InitializingBean.afterPropertiesSet()或自定义初始化方法之前调用。
1.6.3. 其他Aware接口
除了ApplicationContextAware和BeanNameAware(前面讨论过),Spring还提供了广泛的Aware回调接口,让bean向容器指示它们需要某种基础设施依赖关系。作为一般规则,名称表示依赖项类型。下表总结了最重要的Aware接口:
| Name | Injected Dependency | Explained in… |
|---|---|---|
ApplicationContextAware | Declaring ApplicationContext. | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | Event publisher of the enclosing ApplicationContext. | Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware | Class loader used to load the bean classes. | Instantiating Beans |
BeanFactoryAware | Declaring BeanFactory. | The BeanFactory API |
BeanNameAware | Name of the declaring bean. | ApplicationContextAware and BeanNameAware |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time. | Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization). | Additional Capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX notification publisher. | Notifications |
ResourceLoaderAware | Configured loader for low-level access to resources. | Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware SpringApplicationContext. | Spring MVC |
ServletContextAware | Current ServletContext the container runs in. Valid only in a web-aware SpringApplicationContext. | Spring MVC |
请再次注意,使用这些接口将您的代码绑定到Spring API,并且不遵循控制反转风格。因此,对于需要以编程方式访问容器的基础设施bean,我们推荐使用它们。
1.7. Bean定义继承
bean定义可以包含大量配置信息,包括构造函数参数、属性值和特定于容器的信息,比如初始化方法、静态工厂方法名,等等。子bean定义从父bean定义继承配置数据。子定义可以覆盖某些值,也可以根据需要添加其他值。使用父bean和子bean定义可以节省大量输入。实际上,这是一种模板形式。
如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不在这个级别上使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中声明性地配置bean定义。当您使用基于xml的配置元数据时,您可以通过使用父属性来指示子bean定义,将父bean指定为该属性的值。下面的例子展示了如何这样做:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
子bean定义使用来自父bean定义的bean类(如果没有指定的话),但也可以重写它。在后一种情况下,子bean类必须与父bean类兼容(也就是说,它必须接受父bean的属性值)。
子bean定义继承父bean的作用域、构造函数参数值、属性值和方法重写,并可以选择添加新值。您指定的任何范围、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父设置。
其余设置始终取自子定义:依赖、自动连接模式、依赖项检查、单例和惰性初始化。
前面的例子通过使用抽象属性显式地将父bean定义标记为抽象。如果父bean定义没有指定类,则需要显式地将父bean定义标记为抽象,如下例所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean不能单独实例化,因为它是不完整的,而且它还被显式地标记为抽象。当一个定义是抽象的,它只能作为纯模板bean定义使用,用作子定义的父定义。试图单独使用这样一个抽象的父bean,比如将其引用为另一个bean的ref属性,或者使用父bean ID显式调用getBean(),将返回一个错误。类似地,容器的内部preInstantiateSingletons()方法会忽略被定义为抽象的bean定义。
默认情况下,ApplicationContext预实例化所有单例对象。因此,重要的是(至少对于单例bean),如果您有一个(父)bean定义,您打算仅将其用作模板,并且该定义指定了一个类,那么您必须确保将抽象属性设置为true,否则应用程序上下文将实际(尝试)预先实例化抽象bean。