定制 Bean 的性质

Spring 框架提供了许多接口,你可以使用它们来定制 bean 的性质。本节将它们分组如下:

生命周期回调

为了与容器的 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() {
		// do some initialization work
	}
}
class ExampleBean {

	fun init() {
		// do some initialization work
	}
}

前面的示例与以下示例(由两个列表组成)几乎具有完全相同的效果:

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

  • Kotlin

public class AnotherExampleBean implements InitializingBean {

	@Override
	public void afterPropertiesSet() {
		// do some initialization work
	}
}
class AnotherExampleBean : InitializingBean {

	override fun afterPropertiesSet() {
		// do some initialization work
	}
}

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

请注意,@PostConstruct 和初始化方法通常在容器的单例创建锁内执行。bean 实例只有在从 @PostConstruct 方法返回后才被认为是完全初始化并准备好发布给其他对象。此类单独的初始化方法仅用于验证配置状态,并可能根据给定配置准备一些数据结构,但不再进行外部 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() {
		// do some destruction work (like releasing pooled connections)
	}
}
class ExampleBean {

	fun cleanup() {
		// do some destruction work (like releasing pooled connections)
	}
}

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

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

  • Kotlin

public class AnotherExampleBean implements DisposableBean {

	@Override
	public void destroy() {
		// do some destruction work (like releasing pooled connections)
	}
}
class AnotherExampleBean : DisposableBean {

	override fun destroy() {
		// do some destruction work (like releasing pooled connections)
	}
}

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

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

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

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

默认初始化和销毁方法

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

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

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

  • Java

  • Kotlin

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.");
		}
	}
}
class DefaultBlogService : BlogService {

	private var blogDao: BlogDao? = null

	// this is (unsurprisingly) the initialization callback method
	fun init() {
		if (blogDao == null) {
			throw 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 类有这样的方法,它会在适当的时候被调用。

你可以通过类似的方式配置销毁方法回调(在 XML 中),使用顶级 <beans/> 元素上的 default-destroy-method 属性。

如果现有的 bean 类已经有与约定不符的回调方法名称,你可以通过指定(在 XML 中)<bean/> 本身的 init-methoddestroy-method 属性来覆盖默认值。

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 定义的 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>

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

		// 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...
	}
}
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
	val ctx = 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...
}

线程安全和可见性

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

因此,应用程序提供的 bean 类不必担心其初始化状态的可见性。常规配置字段不必标记为 volatile,只要它们只在初始化阶段被修改,即使对于在初始阶段可变的基于 setter 的配置状态,也能提供类似于 final 的可见性保证。如果此类字段在 bean 创建阶段及其后续的初始发布之后发生更改,则需要在访问时将其声明为 volatile 或由公共锁保护。

请注意,在容器侧安全地初始发布之后,对单例 bean 实例中此类配置状态的并发访问(例如,对于控制器实例或存储库实例)是完全线程安全的。这包括在通用单例锁内处理的常见单例 FactoryBean 实例。

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

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

ApplicationContextAwareBeanNameAware

ApplicationContext 创建一个实现 org.springframework.context.ApplicationContextAware 接口的对象实例时,该实例将获得对该 ApplicationContext 的引用。以下列表显示了 ApplicationContextAware 接口的定义:

public interface ApplicationContextAware {

	void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean 可以通过 ApplicationContext 接口以编程方式操作创建它们的 ApplicationContext,或者通过将引用转换为此接口的已知子类(例如 ConfigurableApplicationContext,它公开了附加功能)。一个用途是编程方式检索其他 bean。有时此功能很有用。然而,通常你应该避免它,因为它将代码与 Spring 耦合,并且不遵循控制反转的风格,即协作者作为属性提供给 bean。ApplicationContext 的其他方法提供对文件资源的访问、发布应用程序事件以及访问 MessageSource。这些附加功能在 ApplicationContext 的附加功能中描述。

自动装配是获取 ApplicationContext 引用的另一种替代方法。_传统_的 constructorbyType 自动装配模式(如 自动装配协作者中所述)可以分别为构造函数参数或 setter 方法参数提供 ApplicationContext 类型的依赖项。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注解的自动装配功能。如果你这样做,如果字段、构造函数或方法带有 @Autowired 注解,并且期望 ApplicationContext 类型,则 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 接口:

Table 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。