自定义 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和一般初始化方法在容器的单例创建锁内执行。只有在从@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() {
		// 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 类上的公共closeshutdown方法,以用于特定 bean 定义。您也可以在<beans>元素的default-destroy-method属性上设置此特殊的(inferred)值,以将此行为应用于一组完整的 bean 定义(请参阅默认初始化和销毁方法)。

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

默认初始化和销毁方法

当您编写不使用 Spring 特定的 `InitializingBean` 和 `DisposableBean` 回调接口的初始化和销毁方法回调时,通常会编写名称为 `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;
	}

	// 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 类具有此类方法,则会在适当的时间调用它。

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

在现有 bean 类已经具有与约定不一致的回调方法的情况下,您可以通过(在 XML 中)使用 `<bean/>` 本身的 `init-method` 和 `destroy-method` 属性来指定方法名称以覆盖默认值。

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

组合生命周期机制

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

  • `InitializingBean``DisposableBean` 回调接口

  • 自定义的 init()destroy() 方法

  • @PostConstruct@PreDestroy 注解

    1. 您可以将这些机制结合起来以控制给定的 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 首先会收到停止通知,然后才会传播一般的销毁回调。但是,在上下文生命周期内的热刷新或停止刷新尝试中,只会调用销毁方法。

启动和关闭调用的顺序可能很重要。如果两个对象之间存在“依赖”关系,则依赖方在其依赖项之后启动,并在其依赖项之前停止。但是,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前启动。在这些情况下,SmartLifecycle 接口定义了另一个选项,即getPhase() 方法,如其超接口Phased 中定义的那样。以下清单显示了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() 一样,但它发生在上下文关闭时。另一方面,'refresh' 回调使 SmartLifecycle bean 的另一个功能成为可能。当上下文刷新时(在所有对象实例化并初始化之后),将调用该回调函数。此时,默认生命周期处理器会检查每个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值。如果为 true,则该对象将在此时启动,而不是等待显式调用上下文的或其自身的 start() 方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。phase 值和任何“depends-on”关系决定了启动顺序,如前所述。

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

本节仅适用于非 Web 应用程序。Spring 的基于 Web 的 ApplicationContext 实现已经包含代码,用于在相关 Web 应用程序关闭时优雅地关闭 Spring IoC 容器。

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

要注册一个关闭钩子,请调用 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 接口或通过将引用强制转换为该接口的已知子类(例如 ConfigurableApplicationContext,它公开了其他功能)来以编程方式操作创建它们的 ApplicationContext。一种用途是编程方式检索其他 bean。有时这种能力很有用。但是,一般来说,您应该避免它,因为它将代码耦合到 Spring,并且不遵循控制反转风格,在这种风格中,协作者作为属性提供给 bean。ApplicationContext 的其他方法提供对文件资源的访问、发布应用程序事件以及访问 MessageSource。这些附加功能在 ApplicationContext 的附加功能 中描述。

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