Customizing the Nature of a Bean

The Spring Framework provides a number of interfaces you can use to customize the nature of a bean. This section groups them as follows:

生命周期回调

要与容器的bean生命周期管理进行交互,您可以实现Spring的InitializingBeanDisposableBean接口。容器调用前者的afterPropertiesSet()和后者的destroy()来让bean在初始化和销毁时执行某些操作。

JSR-250的@PostConstruct@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的bean不会与Spring特定的接口耦合。有关详细信息,请参见使用@PostConstruct@PreDestroy

如果您不想使用JSR-250注解,但仍希望消除耦合,可以考虑使用init-methoddestroy-method bean定义元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring默认不提供,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点

除了初始化和销毁回调之外,Spring管理的对象还可以实现Lifecycle接口,以便这些对象可以参与启动和关闭过程,由容器自身的生命周期驱动。

本节描述了生命周期回调接口。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean的所有必要属性之后执行初始化工作。 InitializingBean接口指定了一个方法:

void afterPropertiesSet() throws Exception;

我们建议不要使用InitializingBean接口,因为它会不必要地将代码与Spring耦合。相反,我们建议使用@PostConstruct注解或指定一个POJO初始化方法。在基于XML的配置元数据的情况下,您可以使用init-method属性来指定具有void无参数签名的方法的名称。对于Java配置,您可以使用@BeaninitMethod属性。请参见接收生命周期回调。考虑以下示例:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
  • Java

  • Kotlin

public class ExampleBean {

	public void init() {
		// 进行一些初始化工作
	}
}
class ExampleBean {

	fun init() {
		// 进行一些初始化工作
	}
}

前面的示例几乎与以下示例具有相同的效果(包含两个列表):

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
  • Java

  • Kotlin

public class AnotherExampleBean implements InitializingBean {

	@Override
	public void afterPropertiesSet() {
		// 进行一些初始化工作
	}
}
class AnotherExampleBean : InitializingBean {

	override fun afterPropertiesSet() {
		// 进行一些初始化工作
	}
}

然而,前面两个示例中的第一个不会将代码与Spring耦合。

请注意,@PostConstruct和初始化方法通常在容器的单例创建锁内执行。只有在从@PostConstruct方法返回后,bean实例才被视为完全初始化并准备好发布给其他对象。这些单独的初始化方法仅用于验证配置状态,并可能根据给定配置准备一些数据结构,但不涉及与外部bean访问的进一步活动。否则会存在初始化死锁的风险。

对于需要触发昂贵的后初始化活动的情况,例如异步数据库准备步骤,您的bean应该实现SmartInitializingSingleton.afterSingletonsInstantiated()或依赖于上下文刷新事件:实现ApplicationListener<ContextRefreshedEvent>或声明其注解等效@EventListener(ContextRefreshedEvent.class)。这些变体在所有常规单例初始化之后出现,因此在任何单例创建锁之外。

或者,您可以实现(Smart)Lifecycle接口,并与容器的整体生命周期管理集成,包括自动启动机制、预销毁停止步骤和潜在的停止/重新启动回调(见下文)。

销毁回调

实现org.springframework.beans.factory.DisposableBean接口让一个bean在包含它的容器被销毁时获得一个回调。 DisposableBean接口指定了一个方法:

void destroy() throws Exception;

我们建议不要使用DisposableBean回调接口,因为它会不必要地将代码与Spring耦合。相反,我们建议使用@PreDestroy注解或指定一个被bean定义支持的通用方法。在基于XML的配置元数据中,您可以在<bean/>上使用destroy-method属性。在Java配置中,您可以使用@BeandestroyMethod属性。参见接收生命周期回调。考虑以下定义:

<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>
  • Java

  • Kotlin

public class ExampleBean {

	public void cleanup() {
		// 进行一些销毁工作(如释放连接池连接)
	}
}
class ExampleBean {

	fun cleanup() {
		// 进行一些销毁工作(如释放连接池连接)
	}
}

前面的定义几乎与以下定义具有相同的效果:

<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
  • Java

  • Kotlin

public class AnotherExampleBean implements DisposableBean {

	@Override
	public void destroy() {
		// 进行一些销毁工作(如释放连接池连接)
	}
}
class AnotherExampleBean : DisposableBean {

	override fun destroy() {
		// 进行一些销毁工作(如释放连接池连接)
	}
}

然而,前面两个定义中的第一个不会将代码与Spring耦合。

请注意,Spring还支持推断销毁方法,检测公共的closeshutdown方法。这是Java配置类中@Bean方法的默认行为,并自动匹配java.lang.AutoCloseablejava.io.Closeable实现,也不会将销毁逻辑与Spring耦合。

对于XML中的销毁方法推断,您可以将<bean>元素的destroy-method属性分配一个特殊的(inferred)值,这会指示Spring自动检测bean类上的公共closeshutdown方法以用于特定bean定义。您还可以在<beans>元素的default-destroy-method属性上设置这个特殊的(inferred)值,以将此行为应用于整套bean定义(参见默认初始化和销毁方法)。

对于扩展的关闭阶段,您可以实现Lifecycle接口并在任何单例bean的销毁方法调用之前接收一个早期停止信号。您还可以实现SmartLifecycle以进行一个时间限制的停止步骤,容器将等待所有这样的停止处理完成后再继续执行销毁方法。

默认初始化和销毁方法

当您编写初始化和销毁方法回调时,不使用Spring特定的InitializingBeanDisposableBean回调接口,通常会编写诸如init()initialize()dispose()等名称的方法。理想情况下,这些生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

您可以配置Spring容器在每个bean上“查找”命名的初始化和销毁回调方法名称。这意味着您作为应用程序开发人员可以编写应用程序类并使用名为init()的初始化回调,而无需为每个bean定义配置init-method="init"属性。Spring IoC容器在创建bean时调用该方法(并根据先前描述的标准生命周期回调合同进行调用)。此功能还强制执行初始化和销毁方法回调的一致命名约定。

假设您的初始化回调方法命名为init(),销毁回调方法命名为destroy()。您的类将类似于以下示例中的类:

  • Java

  • Kotlin

public class DefaultBlogService implements BlogService {

	private BlogDao blogDao;

	public void setBlogDao(BlogDao blogDao) {
		this.blogDao = blogDao;
	}

	// 这是(不出所料的)初始化回调方法
	public void init() {
		if (this.blogDao == null) {
			throw new IllegalStateException("必须设置[blogDao]属性。");
		}
	}
}
class DefaultBlogService : BlogService {

	private var blogDao: BlogDao? = null

	// 这是(不出所料的)初始化回调方法
	fun init() {
		if (blogDao == null) {
			throw IllegalStateException("必须设置[blogDao]属性。")
		}
	}
}

然后,您可以在类似以下的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-methoddestroy-method属性来覆盖默认设置(在XML中)。

Spring容器保证配置的初始化回调在bean获得所有依赖项后立即调用。因此,初始化回调在原始bean引用上调用,这意味着AOP拦截器等尚未应用于bean。首先完全创建目标bean,然后应用AOP代理(例如)及其拦截器链。如果目标bean和代理是分开定义的,您的代码甚至可以与原始目标bean交互,绕过代理。因此,在init方法中应用拦截器是不一致的,因为这样做会将目标bean的生命周期与其代理或拦截器耦合,并在您的代码直接与原始目标bean交互时留下奇怪的语义。

组合生命周期机制

从Spring 2.5开始,您有三种选项来控制bean的生命周期行为:

如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则按照此注释后列出的顺序运行每个配置的方法。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名称,例如,为初始化方法配置了init(),则该方法只运行一次,如前面部分所述。

为同一个bean配置的多个生命周期机制,具有不同的初始化方法,调用顺序如下:

  1. 使用@PostConstruct注解的方法

  2. InitializingBean回调接口定义的afterPropertiesSet()方法

  3. 自定义配置的init()方法

销毁方法的调用顺序相同:

  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本身是Lifecycle接口的扩展。它还添加了另外两个方法,用于对上下文刷新和关闭做出反应。

请注意,常规的org.springframework.context.Lifecycle接口是显式启动和停止通知的简单契约,不意味着在上下文刷新时自动启动。为了对自动启动进行细粒度控制,并对特定bean进行优雅停止(包括启动和停止阶段),请考虑实现扩展的org.springframework.context.SmartLifecycle接口。

另外,请注意,停止通知不能保证在销毁之前到来。在常规关闭时,所有Lifecycle bean在传播一般销毁回调之前首先接收停止通知。但是,在上下文生命周期内的热刷新或停止刷新尝试时,只调用销毁方法。

启动和关闭调用的顺序很重要。如果任何两个对象之间存在“depends-on”关系,则依赖方在其依赖项之后启动,并在其依赖项之前停止。但有时,直接依赖关系是未知的。您可能只知道某种类型的对象应在另一种类型的对象之前启动。在这种情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的getPhase()方法。下面的清单显示了Phased接口的定义:

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的“正常”Lifecycle对象的默认阶段为0。因此,任何负阶段值表示对象应在这些标准组件之前启动(并在它们之后停止)。对于任何正阶段值,情况相反。

SmartLifecycle定义的停止方法接受一个回调。任何实现必须在该实现的关闭过程完成后调用该回调的run()方法。这使得在必要时进行异步关闭成为可能,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待每个阶段内的对象组调用该回调的超时值。默认每个阶段的超时时间为30秒。您可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的生命周期处理器实例。如果只想修改超时时间,则定义以下内容即可:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
	<!-- 超时值(毫秒) -->
	<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程,就好像显式调用了stop()一样,但是发生在上下文关闭时。另一方面,“刷新”回调使SmartLifecycle bean的另一个功能生效。当上下文刷新时(在所有对象都已实例化和初始化之后),将调用该回调。此时,默认生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为true,则该对象在此时启动,而不是等待上下文或其自身的start()方法的显式调用(与上下文刷新不同,标准上下文实现不会自动进行上下文启动)。阶段值和任何“depends-on”关系确定了如前所述的启动顺序。

在非Web应用程序中优雅地关闭Spring IoC容器

此部分仅适用于非Web应用程序。Spring基于Web的ApplicationContext实现已经具有在相关Web应用程序关闭时优雅关闭Spring IoC容器的代码。

如果您在非Web应用程序环境中(例如在富客户端桌面环境中)使用Spring的IoC容器,请向JVM注册一个关闭挂钩。这样可以确保优雅地关闭并调用单例bean上的相关销毁方法,以释放所有资源。您仍然必须正确配置和实现这些销毁回调。

要注册一个关闭挂钩,请调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下例所示:

  • Java

  • Kotlin

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");

		// 为上述上下文添加一个关闭挂钩...
		ctx.registerShutdownHook();

		// 应用程序在此运行...

		// 主方法退出前,挂钩将在应用程序关闭之前被调用...
	}
}
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
	val ctx = ClassPathXmlApplicationContext("beans.xml")

	// 为上述上下文添加一个关闭挂钩...
	ctx.registerShutdownHook()

	// 应用程序在此运行...

	// 主方法退出前,挂钩将在应用程序关闭之前被调用...
}

线程安全和可见性

Spring核心容器以线程安全的方式发布创建的单例实例,通过单例锁保护访问,并确保在其他线程中可见。

因此,应用程序提供的bean类无需关注其初始化状态的可见性。只要在初始化阶段仅对常规配置字段进行变异,常规配置字段就无需标记为volatile,即使对于在初始阶段可变的基于setter的配置状态,也提供类似于final的可见性保证。如果这些字段在bean创建阶段及其后的初始发布之后发生更改,则需要将其声明为volatile或在访问时由公共锁保护。

请注意,在单例bean实例中对这种配置状态的并发访问(例如对控制器实例或存储库实例)在从容器方面进行安全初始发布后是完全线程安全的。这还包括在一般单例锁内处理的常见单例FactoryBean实例。

对于销毁回调,配置状态仍然是线程安全的,但在初始化和销毁之间累积的任何运行时状态应保留在线程安全结构中(或对于简单情况,保留在volatile字段中),按照常见的Java指南。

如上所示,更深入的Lifecycle集成涉及到可运行字段等运行时可变状态,这些状态必须声明为volatile。虽然常见的生命周期回调遵循一定的顺序,例如,启动回调保证仅在完全初始化后发生,停止回调仅在初始启动后发生,但在常见的停止前销毁安排中存在一个特殊情况:强烈建议任何这种bean中的内部状态也允许在没有先前停止的情况下立即进行销毁回调,因为这可能发生在取消引导后的特殊关闭期间或由另一个bean引起的停止超时情况。

ApplicationContextAwareBeanNameAware

当一个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的其他功能中有描述。

自动装配是获得对ApplicationContext的引用的另一种选择。传统的constructorbyType自动装配模式(如自动装配协作者中所述)可以为构造函数参数或setter方法参数提供类型为ApplicationContext的依赖项。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,可以使用基于注解的自动装配功能。如果这样做,如果字段、构造函数或方法携带@Autowired注解,则期望ApplicationContext类型的字段、构造函数或方法参数将自动装配到该字段、构造函数或方法中。有关更多信息,请参见使用@Autowired

当一个ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将获得一个对其关联对象定义中定义的名称的引用。以下清单显示了BeanNameAware接口的定义:

public interface BeanNameAware {

	void setBeanName(String name) throws BeansException;
}

在填充普通bean属性之后但在初始化回调(如InitializingBean.afterPropertiesSet()或自定义init-method)之前调用回调。

其他Aware接口

除了ApplicationContextAwareBeanNameAware(在前面讨论过),Spring提供了许多Aware回调接口,让bean指示容器它们需要某种基础设施依赖。一般规则是,名称指示依赖类型。以下表总结了最重要的Aware接口:

表1. Aware接口
名称 注入的依赖项 解释在...​​

ApplicationContextAware

声明ApplicationContext

ApplicationContextAwareBeanNameAware

ApplicationEventPublisherAware

封闭ApplicationContext的事件发布者。

ApplicationContext的其他功能

BeanClassLoaderAware

用于加载bean类的类加载器。

实例化Bean

BeanFactoryAware

声明BeanFactory

BeanFactory API

BeanNameAware

声明bean的名称。

ApplicationContextAwareBeanNameAware

LoadTimeWeaverAware

用于在加载时处理类定义的定义编织器。

Spring框架中使用AspectJ进行加载时编织

MessageSourceAware

用于解析消息的配置策略(支持参数化和国际化)。

ApplicationContext的其他功能

NotificationPublisherAware

Spring JMX通知发布者。

通知

ResourceLoaderAware

用于低级访问资源的配置加载器。

资源

ServletConfigAware

容器运行的当前ServletConfig。仅在Web感知的Spring ApplicationContext中有效。

Spring MVC

ServletContextAware

容器运行的当前ServletContext。仅在Web感知的Spring ApplicationContext中有效。

Spring MVC

再次注意,使用这些接口将您的代码与Spring API绑定在一起,不符合控制反转的风格。因此,我们建议将它们用于需要以编程方式访问容器的基础设施bean。