定制 Bean 的性质

Spring Framework 提供了许多接口,您可以使用它们来定制 Bean 的性质。本节将它们分组如下

生命周期回调

为了与容器对 Bean 生命周期的管理进行交互,您可以实现 Spring 的 InitializingBeanDisposableBean 接口。容器会为前者调用 afterPropertiesSet() 方法,为后者调用 destroy() 方法,以便 Bean 在初始化和销毁时执行特定操作。

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

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

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

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

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

初始化回调

org.springframework.beans.factory.InitializingBean 接口允许 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 定义对应的 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);
}

启动时,phase 值最低的对象首先启动。停止时,顺序相反。因此,实现了 SmartLifecycle 并且其 getPhase() 方法返回 Integer.MIN_VALUE 的对象将在启动时排在最前面,在停止时排在最后面。另一方面,phase 值为 Integer.MAX_VALUE 将表示对象应最后启动,最先停止(可能是因为它依赖于其他正在运行的进程)。考虑 phase 值时,知道任何未实现 SmartLifecycle 的“正常” Lifecycle 对象的默认 phase 为 0 也很重要。因此,任何负的 phase 值都表示对象应在这些标准组件之前启动(并在它们之后停止)。任何正的 phase 值则相反。

SmartLifecycle 定义的 stop 方法接受一个回调。任何实现都必须在该实现的关闭过程完成后调用该回调的 run() 方法。这样可以在必要时实现异步关闭,因为 LifecycleProcessor 接口的默认实现 DefaultLifecycleProcessor 会在其超时值内等待每个 phase 中的对象组调用该回调。每个 phase 的默认超时时间是 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 上相关的销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。

要注册关机钩子,请调用 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 集成涉及运行时可变状态,例如必须声明为 volatilerunnable 字段。虽然常见的生命周期回调遵循特定顺序,例如,启动回调保证只在完全初始化后发生,停止回调保证只在初始启动后发生,但存在一个特殊情况,即常见的停止在销毁之前的安排:强烈建议任何此类 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,并且不遵循控制反转(Inversion of Control)的风格,在该风格中,协作对象作为属性提供给 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 方法之类的初始化回调之前被调用。

其他 Aware 接口

除了 ApplicationContextAwareBeanNameAware(前面讨论过)之外,Spring 还提供了广泛的 Aware 回调接口,让 Bean 可以向容器表明它们需要某个基础架构依赖。通常,名称表示依赖类型。下表总结了最重要的 Aware 接口

表 1. Aware 接口
名称 注入的依赖 解释参见…​

ApplicationContextAware

声明 Bean 的 ApplicationContext

ApplicationContextAwareBeanNameAware

ApplicationEventPublisherAware

封闭的 ApplicationContext 的事件发布器。

ApplicationContext 的附加功能

BeanClassLoaderAware

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

实例化 Bean

BeanFactoryAware

声明 Bean 的 BeanFactory

BeanFactory API

BeanNameAware

声明 Bean 的名称。

ApplicationContextAwareBeanNameAware

LoadTimeWeaverAware

用于在加载时处理类定义的已定义 weaver。

在 Spring Framework 中使用 AspectJ 进行加载时织入

MessageSourceAware

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

ApplicationContext 的附加功能

NotificationPublisherAware

Spring JMX 通知发布器。

通知

ResourceLoaderAware

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

资源

ServletConfigAware

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

Spring MVC

ServletContextAware

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

Spring MVC

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