自定义 Bean 的性质
Spring 框架提供了一些接口,您可以使用它们来定制 Bean 的本质。本节将它们分组如下
生命周期回调
为了与容器管理的 Bean 生命周期进行交互,您可以实现 Spring 的 InitializingBean
和 DisposableBean
接口。容器会分别调用前者的 afterPropertiesSet()
和后者的 destroy()
方法,让 Bean 在初始化和销毁时执行某些操作。
JSR-250 的 如果您不想使用 JSR-250 注解,但仍然希望消除耦合,请考虑 |
在内部,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 配置,您可以使用@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 类上的公共close 或shutdown 方法,以用于特定 bean 定义。您也可以在<beans> 元素的default-destroy-method 属性上设置此特殊的(inferred) 值,以将此行为应用于一组完整的 bean 定义(请参阅默认初始化和销毁方法)。
|
对于扩展的关闭阶段,您可以实现 |
默认初始化和销毁方法
当您编写不使用 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 生命周期行为
-
自定义的
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
接口的扩展。它还添加了另外两种方法来响应上下文刷新和关闭。
请注意,常规的 此外,请注意,停止通知不能保证在销毁之前到来。在常规关闭时,所有 |
启动和关闭调用的顺序可能很重要。如果两个对象之间存在“依赖”关系,则依赖方在其依赖项之后启动,并在其依赖项之前停止。但是,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前启动。在这些情况下,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 的 |
如果您在非 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 导致的停止超时的情况下发生。
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
引用的另一种方法。传统的构造函数
和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
接口
除了ApplicationContextAware
和BeanNameAware
(在前面讨论过)之外,Spring还提供了一系列Aware
回调接口,让bean可以向容器表明它们需要特定的基础设施依赖项。一般来说,名称表示依赖项类型。下表总结了最重要的Aware
接口
名称 | 注入的依赖项 | 在…中解释 |
---|---|---|
|
声明 |
|
|
封闭 |
|
|
用于加载bean类的类加载器。 |
|
|
声明 |
|
|
声明bean的名称。 |
|
|
定义的织入器,用于在加载时处理类定义。 |
|
|
配置的策略,用于解析消息(支持参数化和国际化)。 |
|
|
Spring JMX 通知发布器。 |
|
|
配置加载器以低级别访问资源。 |
|
|
容器运行的当前 |
|
|
容器运行的当前 |
再次注意,使用这些接口会将您的代码绑定到 Spring API,并且不遵循控制反转风格。因此,我们建议将其用于需要以编程方式访问容器的基础设施 Bean。