定制 Bean 的性质
Spring Framework 提供了许多接口,您可以使用它们来定制 Bean 的性质。本节将它们分组如下
生命周期回调
为了与容器对 Bean 生命周期的管理进行交互,您可以实现 Spring 的 InitializingBean 和 DisposableBean 接口。容器会为前者调用 afterPropertiesSet() 方法,为后者调用 destroy() 方法,以便 Bean 在初始化和销毁时执行特定操作。
|
在现代 Spring 应用中,JSR-250 的 如果您不想使用 JSR-250 注解但仍想解除耦合,可以考虑使用 |
在内部,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 配置,您可以使用 @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 耦合。
|
请注意, 对于需要触发耗时后初始化活动(例如,异步数据库准备步骤)的场景,您的 Bean 应该实现 或者,您可以实现 |
销毁回调
实现 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 定义对应的 Bean 类上的公共 close 或 shutdown 方法。您也可以在 <beans> 元素的 default-destroy-method 属性上设置这个特殊的 (inferred) 值,将此行为应用于整组 Bean 定义(参见默认初始化和销毁方法)。 |
|
对于扩展的关闭阶段,您可以实现 |
默认初始化和销毁方法
当您编写不使用 Spring 特定的 InitializingBean 和 DisposableBean 回调接口的初始化和销毁方法回调时,通常会编写名为 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-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注解-
您可以组合这些机制来控制给定的 Bean。
-
如果为一个 Bean 配置了多个生命周期机制,并且每个机制配置了不同的方法名,则每个配置的方法将按本注意事项后列出的顺序运行。但是,如果为这些生命周期机制中的多个配置了相同的方法名(例如,初始化方法都配置为 init()),则该方法只运行一次,如前一节所述。 |
为同一个 Bean 配置的、具有不同初始化方法的多个生命周期机制,将按如下顺序调用
-
用
@PostConstruct注解的方法 -
InitializingBean回调接口定义的afterPropertiesSet()方法 -
自定义配置的
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 接口的扩展。它还增加了另外两个方法,用于响应上下文的刷新和关闭。
|
请注意,常规的 另外请注意,停止通知不保证在销毁之前发生。在常规关机时,所有 |
启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在“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 的 |
如果您在非 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 集成涉及运行时可变状态,例如必须声明为 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,并且不遵循控制反转(Inversion of Control)的风格,在该风格中,协作对象作为属性提供给 Bean。ApplicationContext 的其他方法提供了文件资源访问、发布应用程序事件以及访问 MessageSource 的能力。这些附加特性在ApplicationContext 的附加功能中进行了描述。
自动装配是获取对 ApplicationContext 引用的另一种替代方法。传统的 constructor 和 byType 自动装配模式(如自动装配协作对象中所述)可以分别为构造函数参数或 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 接口
除了 ApplicationContextAware 和 BeanNameAware(前面讨论过)之外,Spring 还提供了广泛的 Aware 回调接口,让 Bean 可以向容器表明它们需要某个基础架构依赖。通常,名称表示依赖类型。下表总结了最重要的 Aware 接口
| 名称 | 注入的依赖 | 解释参见… |
|---|---|---|
|
声明 Bean 的 |
|
|
封闭的 |
|
|
用于加载 Bean 类的类加载器。 |
|
|
声明 Bean 的 |
|
|
声明 Bean 的名称。 |
|
|
用于在加载时处理类定义的已定义 weaver。 |
|
|
用于解析消息的已配置策略(支持参数化和国际化)。 |
|
|
Spring JMX 通知发布器。 |
|
|
用于低级别访问资源的已配置加载器。 |
|
|
容器运行时的当前 |
|
|
容器运行时的当前 |
再次注意,使用这些接口会将您的代码与 Spring API 绑定,并且不遵循控制反转的风格。因此,我们建议将它们用于需要以编程方式访问容器的基础设施 Bean。