Customizing the Nature of a Bean

生命周期回调接口允许 bean 执行诸如在初始化和销毁时执行任务的操作。

有感知的接口使 bean 能够访问容器提供的服务,例如 ApplicationContext 和 BeanClassLoader。

有感知的接口包括:

  • ApplicationContextAware:提供对 ApplicationContext 的访问。

  • BeanNameAware:提供对 bean 名称的访问。

  • BeanClassLoaderAware:提供对类加载器的访问。

  • BeanFactoryAware:提供对 BeanFactory 的访问。

Spring Framework 提供了许多接口,可用于自定义 bean 的本质。此部分将其分组如下:

Lifecycle Callbacks

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

JSR-250 @PostConstruct@PreDestroy 注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 Bean 与特定的 Spring 接口无关。有关详细信息,请参阅 Using @PostConstruct and @PreDestroy。 如果您不想使用 JSR-250 注解,但仍希望解除耦合,请考虑使用 init-methoddestroy-method bean 定义元数据。

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

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

生命周期回调接口在此部分中描述。

Initialization Callbacks

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

void afterPropertiesSet() throws Exception;

我们建议你不使用 InitializingBean 接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用 @PostConstruct 注释或指定一个 POJO 初始化方法。对于基于 XML 的配置元数据,你可以使用 init-method 属性来指定具有 void 无参数签名的那个方法的名称。对于 Java 配置,你可以使用 @BeaninitMethod 属性。参见 Receiving Lifecycle Callbacks。考虑以下示例:

<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 接口,并与容器的整体生命周期管理集成,包括自动启动机制、预销毁停止步骤以及潜在的停止/重新启动回调(请参见下文)。

Destruction Callbacks

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

void destroy() throws Exception;

我们建议你不使用 DisposableBean 回调接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用 @PreDestroy 注释或指定一个由 bean 定义支持的通用方法。对于基于 XML 的配置元数据,你可以对 <bean/> 中的 destroy-method 属性使用。对于 Java 配置,你可以使用 @BeandestroyMethod 属性。参见 Receiving Lifecycle Callbacks。考虑以下定义:

<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 定义(请参阅 Default Initialization and Destroy Methods)。

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

Default Initialization and Destroy Methods

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

你可以配置 Spring 容器以 “look” 初始化和销毁方法名称上的每个 Bean。这意味着你作为应用程序开发人员,可以编写你的应用程序类并使用调用为 init() 的初始化回调方法,而无需使用每个 Bean 定义配置 init-method="init" 属性。当创建 Bean 时(并按照标准生命周期回调约定 described previously),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 类具有此类方法,则它将在适当的时候被调用。

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

在现有的 bean 类已拥有与约定不相符的命名回调方法的情况下,您可以通过(即在 XML 中)使用 <bean/> 本身的 init-methoddestroy-method 属性指定方法名称,从而覆盖默认值。

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

Combining Lifecycle Mechanisms

从 Spring 2.5 开始,您有三个选项用于控制 bean 生命周期行为:

如果为 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个已配置的方法都将在此注释后面列出的顺序中运行。但是,如果为多个此类生命周期机制配置了相同的方法名称 - 例如,某个初始化方法的 init() - 那么该方法将运行一次,如 preceding section 中所解释的。

为同一 bean 配置了多个生命周期机制,具有不同的初始化方法,它们将按如下方式调用:

  1. Methods annotated with @PostConstruct

  2. afterPropertiesSet(),如 `InitializingBean`回调接口所定义

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

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

  1. Methods annotated with @PreDestroy

  2. destroy(),如 `DisposableBean`回调接口所定义

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

Startup and Shutdown Callbacks

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 的对象将是最先启动和最后停止的对象。另一方面,一个 phase 值为 Integer.MAX_VALUE 表示对象应该最后启动并首先停止(可能是因为它依赖于其他正在运行的进程)。考虑 phase 值时,还必须知道未实现 SmartLifecycle 的任何“normalLifecycle 对象的默认 phase 为 0。因此,任何负 phase 值都表示一个对象应该在这些标准组件之前启动(并在它们之后停止)。任何正 phase 值都是相反的。

SmartLifecycle 定义的 stop 方法接受一个回调。在该实现的关闭进程完成后,任何实现都必须调用该回调的 run() 方法。由于 LifecycleProcessor 接口的默认实现 DefaultLifecycleProcessor 会为每个 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”关系确定启动顺序。

Shutting Down the Spring IoC Container Gracefully in Non-Web Applications

此部分仅适用于非 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...
}

Thread Safety and Visibility

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

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

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

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

如上所示,更深入的 Lifecycle 集成涉及运行时可变状态,例如必须声明为 volatilerunnable 字段。虽然公共生命周期回调遵循特定顺序(例如,保证只有在完全初始化后才发生启动回调,并且只有在初始启动后才发生停止回调),但有一个特殊情况是销毁之前的常用停止安排:强烈建议这种情况下的任何 Bean 中的内部状态也允许立即销毁回调,而无需先停止,因为这可能会在取消引导后或在由另一个 Bean 导致的停止超时的情况下发生的非正常关闭期间发生。

ApplicationContextAware and 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 的访问权限。这些附加特性在Additional Capabilities of the ApplicationContext 中进行了说明。

自动装配是获得对`ApplicationContext`的引用方法的另一种替代方法。traditional constructorbyType`自动装配模式(如 Autowiring Collaborators 中描述)可以为构造函数参数或 setter 方法参数提供类型为`ApplicationContext`的依赖项。对于包括自动装配字段和多参数方法的功能在内的更多灵活性,请使用基于注释的自动装配特性。如果您这样做,如果相关字段、构造函数或方法带有 `@Autowired 注释,则无论 ApplicationContext 是否自动装配到字段、构造函数参数或方法参数,都期望 ApplicationContext 类型。有关更多信息,请参阅 Using @Autowired

ApplicationContext 创建实现 org.springframework.beans.factory.BeanNameAware 接口的类时,会向该类提供对其在关联对象定义中定义的名称的引用。以下清单显示 BeanNameAware 接口的定义:

public interface BeanNameAware {

	void setBeanName(String name) throws BeansException;
}

回调在设置常规 bean 属性后但在初始化回调(例如 InitializingBean.afterPropertiesSet() 或自定义初始化方法)之前调用。

Other Aware Interfaces

除了 ApplicationContextAwareBeanNameAware (见 earlier),Spring 提供了广泛的 Aware 回调接口,这些接口允许 Bean 指示容器它们需要特定的基础架构依赖性。作为一般规则,名称表示依赖类型。下表总结了最重要的 Aware 接口:

Table 1. Aware interfaces
Name Injected Dependency Explained in…​

ApplicationContextAware

Declaring ApplicationContext.

ApplicationContextAware and BeanNameAware

ApplicationEventPublisherAware

ApplicationContext 的事件发布器。

Additional Capabilities of the ApplicationContext

BeanClassLoaderAware

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

Instantiating Beans

BeanFactoryAware

Declaring BeanFactory.

The BeanFactory API

BeanNameAware

声明 Bean 的名称。

ApplicationContextAware and BeanNameAware

LoadTimeWeaverAware

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

Load-time Weaving with AspectJ in the Spring Framework

MessageSourceAware

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

Additional Capabilities of the ApplicationContext

NotificationPublisherAware

Spring JMX notification publisher.

Notifications

ResourceLoaderAware

为资源低级访问配置加载器。

Resources

ServletConfigAware

容器目前`ServletConfig`运行在其中。仅在 web 兼容的 Spring `ApplicationContext`中有效。

Spring MVC

ServletContextAware

容器目前`ServletContext`运行在其中。仅在 web 兼容的 Spring `ApplicationContext`中有效。

Spring MVC

请再次注意,使用这些接口会将您的代码绑定到 Spring API,且不遵循控制反转风格。因此,我们建议将它们用于需要对容器进行编程访问的基础架构 bean。