自定义 Bean 的性质
Spring 框架提供了一些接口,您可以使用这些接口来定制 bean 的性质。本节将它们分组如下:
生命周期回调
要与容器管理 bean 生命周期进行交互,您可以实现 Spring InitializingBean
和 DisposableBean
接口。容器会调用 afterPropertiesSet()
以便于前者在初始化时执行某些操作,而调用 destroy()
以便于后者在销毁时执行某些操作。
JSR-250 的 @PostConstruct
和 @PreDestroy
注解通常被认为是现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 不会与 Spring 特定的接口耦合。有关详细信息,请参见 Using @PostConstruct and @PreDestroy。
如果您不想使用 JSR-250 注解,但仍希望消除耦合,请考虑 init-method
和 destroy-method
bean 定义元数据。
在内部,Spring 框架使用 BeanPostProcessor
实现来处理它能够找到的任何回调接口并调用适当的方法。如果您需要自定义功能或 Spring 默认不提供的其他生命周期行为,您可以自己实现一个 BeanPostProcessor
。有关更多信息,请参见 Container Extension Points。
除了初始化和销毁回调,Spring 管理的对象还可以实现 Lifecycle
接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
本节描述了生命周期回调接口。
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许一个 bean 在容器设置完所有必要属性后执行初始化工作。InitializingBean
接口指定了一个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用 InitializingBean
接口,因为它不必要地将代码与 Spring 绑定在一起。相反,我们建议使用 @PostConstruct 注解或指定一个 POJO 初始化方法。在基于 XML 的配置元数据中,您可以使用 init-method
属性来指定具有 void 无参数签名的方法名称。对于 Java 配置,您可以使用 @Bean
的 initMethod
属性。请参见 接收生命周期回调。考虑以下示例:
<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 配置,您可以使用 @Bean
的 destroyMethod
属性。请参见 接收生命周期回调。考虑以下定义:
<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 还支持推断销毁方法,检测公共的 close
或 shutdown
方法。这是 Java 配置类中 @Bean
方法的默认行为,并且自动匹配 java.lang.AutoCloseable
或 java.io.Closeable
实现,而不将销毁逻辑与 Spring 绑定。
对于使用 XML 的销毁方法推断,您可以将 <bean>
元素的 destroy-method
属性赋值为特殊的 (inferred)
值,这指示 Spring 自动检测 bean 类中的公共 close
或 shutdown
方法,以便为特定的 bean 定义。您还可以在 <beans>
元素的 default-destroy-method
属性上设置这个特殊的 (inferred)
值,以将此行为应用于一整套 bean 定义(请参见 默认初始化和销毁方法)。
对于扩展的关闭阶段,您可以实现 Lifecycle
接口,并在任何单例 bean 的销毁方法被调用之前接收早期停止信号。您还可以实现 SmartLifecycle
以进行时间限制的停止步骤,在此步骤中,容器将在所有此类停止处理完成之前等待,然后再继续执行销毁方法。
默认初始化和销毁方法
当你编写不使用 Spring 特定的 InitializingBean
和 DisposableBean
回调接口的初始化和销毁方法回调时,通常会编写名称为 init()
、initialize()
、dispose()
等的方法。理想情况下,这些生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。
您可以配置 Spring 容器以“查找”每个 bean 上命名的初始化和销毁回调方法名称。这意味着作为应用程序开发者,您可以编写应用程序类并使用一个名为 init()
的初始化回调,而无需在每个 bean 定义中配置 init-method="init"
属性。当 bean 被创建时,Spring IoC 容器会调用该方法(并根据之前描述的标准生命周期回调合同 described previously)。此功能还强制执行初始化和销毁方法回调的一致命名约定。
假设你的初始化回调方法命名为 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 类,如果其回调方法的命名与约定不一致,您可以通过在 <bean/>
本身中使用 init-method
和 destroy-method
属性来覆盖默认值(即在 XML 中指定方法名称)。
Spring 容器保证在 bean 被提供所有依赖后,配置的初始化回调会立即被调用。因此,初始化回调是在原始 bean 引用上被调用的,这意味着 AOP 拦截器等尚未应用于该 bean。目标 bean 首先被完全创建,然后应用一个 AOP 代理(例如)及其拦截器链。如果目标 bean 和代理是单独定义的,您的代码甚至可以与原始目标 bean 进行交互,绕过代理。因此,将拦截器应用于 init
方法是不一致的,因为这样做会将目标 bean 的生命周期与其代理或拦截器耦合,并在您的代码直接与原始目标 bean 交互时留下奇怪的语义。
结合生命周期机制
从 Spring 2.5 开始,您有三种选项来控制 Bean 生命周期行为:
-
InitializingBean 和 DisposableBean 回调接口
-
自定义
init()
和destroy()
方法 -
@PostConstruct 和 @PreDestroy 注解
- 你可以结合这些机制来控制一个给定的 bean。
如果为一个 bean 配置了多个生命周期机制,并且每个机制配置了不同的方法名,那么在此注释之后列出的顺序中将依次运行每个配置的方法。然而,如果为多个生命周期机制配置了相同的方法名 — 例如,初始化方法的 init()
— 那么该方法将只运行一次,如 前一节 中所述。
多个生命周期机制为同一个 bean 配置,具有不同的初始化方法,调用如下:
-
用
@PostConstruct
注解的方法 -
afterPropertiesSet()
,由InitializingBean
回调接口定义 -
自定义配置的
init()
方法
销毁方法按相同的顺序调用:
-
使用
@PreDestroy
注解的方法 -
DisposableBean
回调接口定义的destroy()
-
自定义配置的
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
beans 首先会收到停止通知,然后才会传播一般的销毁回调。然而,在上下文生命周期内的热刷新或停止刷新尝试中,仅调用销毁方法。
启动和关闭调用的顺序可能很重要。如果两个对象之间存在“依赖”关系,则依赖方在其依赖项之后启动,并在其依赖项之前停止。然而,有时直接的依赖关系是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前启动。在这些情况下,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()
一样,但它发生在上下文关闭时。另一方面,'refresh' 回调启用了 SmartLifecycle
bean 的另一个特性。当上下文被刷新(在所有对象被实例化和初始化之后),该回调被调用。在这一点上,默认的生命周期处理器检查每个 SmartLifecycle
对象的 isAutoStartup()
方法返回的布尔值。如果返回 true
,那么该对象将在此时启动,而不是等待显式调用上下文的或其自身的 start()
方法(与上下文刷新不同,标准上下文实现的上下文启动不会自动发生)。phase
值和任何“depends-on” 关系决定了启动顺序,如前所述。
在非 Web 应用程序中优雅地关闭 Spring IoC 容器
本节仅适用于非网络应用程序。Spring 的基于 web 的 ApplicationContext
实现已经有代码来优雅地关闭 Spring IoC 容器,当相关的 web 应用程序被关闭时。
如果您在非 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
,这提供了类似于 final
的可见性保证,即使对于在初始阶段可变的基于 setter 的配置状态也是如此。如果这些字段在 bean 创建阶段及其后续初始发布后被更改,则在访问时需要将其声明为 volatile
或由一个公共锁保护。
请注意,对单例 bean 实例中的此类配置状态的并发访问,例如,对控制器实例或仓库实例的访问,在容器端进行安全的初始发布后是完全线程安全的。这包括在一般单例锁内处理的常见单例 FactoryBean
实例。
对于销毁回调,配置状态保持线程安全,但在初始化和销毁之间累积的任何运行时状态应根据常见的 Java 指南保存在线程安全的结构中(或在简单情况下保存在 volatile
字段中)。
更深层次的 Lifecycle
集成如上所示,涉及运行时可变状态,例如一个需要声明为 volatile
的 runnable
字段。虽然常见的生命周期回调遵循一定的顺序,例如,启动回调保证只在完全初始化后发生,而停止回调则仅在初始启动后发生,但在常见的停止前销毁安排中有一个特殊情况:强烈建议任何此类 bean 的内部状态也允许在没有前置停止的情况下立即进行销毁回调,因为这可能发生在取消引导后的异常关闭过程中,或者由于另一个 bean 导致的停止超时。
ApplicationContextAware
和 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
。这些附加功能在 ApplicationContext 的附加功能 中进行了描述。
自动装配是获取 ApplicationContext
引用的另一种选择。传统的 constructor
和 byType
自动装配模式(如 Autowiring Collaborators 中所述)可以为构造函数参数或 setter 方法参数提供 ApplicationContext
类型的依赖。为了获得更多灵活性,包括能够自动装配字段和多个参数的方法,使用基于注解的自动装配特性。如果这样做,ApplicationContext
将被自动装配到期望 ApplicationContext
类型的字段、构造函数参数或方法参数中,只要相关的字段、构造函数或方法带有 @Autowired
注解。有关更多信息,请参见 Using @Autowired。
当 ApplicationContext
创建一个实现 org.springframework.beans.factory.BeanNameAware
接口的类时,该类会获得一个对其关联对象定义中定义的名称的引用。以下列表显示了 BeanNameAware 接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调在普通 bean 属性填充后被调用,但在初始化回调(例如 InitializingBean.afterPropertiesSet()
或自定义 init-method)之前。
其他 Aware
接口
除了 ApplicationContextAware
和 BeanNameAware
(在 之前 讨论过),Spring 提供了广泛的 Aware
回调接口,允许 bean 向容器指示它们需要某种基础设施依赖。一般来说,名称指示依赖类型。以下表格总结了最重要的 Aware
接口:
表 1. 认知接口
名称 | 注入的依赖 | 解释在… |
---|---|---|
ApplicationContextAware | 声明 ApplicationContext 。 | ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware | 包含的 ApplicationContext 的事件发布者。 | ApplicationContext 的附加功能 |
BeanClassLoaderAware | 用于加载 bean 类的类加载器。 | 实例化 Beans |
BeanFactoryAware | 声明 BeanFactory 。 | BeanFactory API |
BeanNameAware | 声明 bean 的名称。 | ApplicationContextAware 和 BeanNameAware |
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。