Customizing the Nature of a Bean
生命周期回调接口允许 bean 执行诸如在初始化和销毁时执行任务的操作。
有感知的接口使 bean 能够访问容器提供的服务,例如 ApplicationContext 和 BeanClassLoader。
有感知的接口包括:
-
ApplicationContextAware:提供对 ApplicationContext 的访问。
-
BeanNameAware:提供对 bean 名称的访问。
-
BeanClassLoaderAware:提供对类加载器的访问。
-
BeanFactoryAware:提供对 BeanFactory 的访问。
Spring Framework 提供了许多接口,可用于自定义 bean 的本质。此部分将其分组如下:
Lifecycle Callbacks
要与容器对 bean 生命周期进行管理进行交互,您可以实现 Spring InitializingBean
和 DisposableBean
接口。容器对前者调用 afterPropertiesSet()
,对后者调用 destroy()
,以便在 bean 初始化和销毁时让 bean 执行某些操作。
JSR-250 |
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 配置,你可以使用 @Bean
的 initMethod
属性。参见 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 耦合在一起。
请注意, |
Destruction Callbacks
实现 org.springframework.beans.factory.DisposableBean
接口使得 bean 能够在包含它的容器被销毁时获取回调。DisposableBean
接口指定了一个方法:
void destroy() throws Exception;
我们建议你不使用 DisposableBean
回调接口,因为它不必要地将代码耦合到 Spring。或者,我们建议使用 @PreDestroy
注释或指定一个由 bean 定义支持的通用方法。对于基于 XML 的配置元数据,你可以对 <bean/>
中的 destroy-method
属性使用。对于 Java 配置,你可以使用 @Bean
的 destroyMethod
属性。参见 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 还支持销毁方法的推断,检测公开的 close
或 shutdown
方法。这是 Java 配置类中的 @Bean
方法的默认行为,并且自动匹配 java.lang.AutoCloseable
或 java.io.Closeable
实现,也不会将销毁逻辑与 Spring 耦合。
对于带有 XML 的销毁方法推断,您可以将 |
对于扩展的关闭阶段,您可以实现 |
Default Initialization and Destroy Methods
当您编写不使用 Spring 特定的 InitializingBean
和 DisposableBean
回调接口的初始化和销毁方法回调时,通常会编写一些名称为 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-method
和 destroy-method
属性指定方法名称,从而覆盖默认值。
Spring 容器保证,在为 bean 提供所有依赖项之后,立即调用配置的初始化回调。因此,初始化回调是在原始 bean 引用上调用的,这意味着尚未将 AOP 拦截器等应用到 bean 上。首先创建目标 bean,然后应用 AOP 代理(例如)及其拦截器链。如果目标 bean 和代理分开定义,则您的代码甚至可以与原始目标 bean 进行交互,从而绕过代理。因此,将拦截器应用于 init
方法是不一致的,因为这样做会将目标 bean 的生命周期与其代理或拦截器耦合,并且当您的代码直接与原始目标 bean 进行交互时,会留下奇怪的语义。
Combining Lifecycle Mechanisms
从 Spring 2.5 开始,您有三个选项用于控制 bean 生命周期行为:
-
自定义 `init()`和 `destroy()`方法
-
@PostConstruct
and@PreDestroy
annotations[style="arabic"]-
你可以结合这些机制来控制给定 bean。
-
如果为 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个已配置的方法都将在此注释后面列出的顺序中运行。但是,如果为多个此类生命周期机制配置了相同的方法名称 - 例如,某个初始化方法的 |
为同一 bean 配置了多个生命周期机制,具有不同的初始化方法,它们将按如下方式调用:
-
Methods annotated with
@PostConstruct
-
afterPropertiesSet()
,如 `InitializingBean`回调接口所定义 -
自定义配置的 `init()`方法
销毁方法按相同顺序调用:
-
Methods annotated with
@PreDestroy
-
destroy()
,如 `DisposableBean`回调接口所定义 -
自定义配置的 `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
接口的扩展。它还增加了另外两种方法来响应上下文被刷新和关闭。
请注意,常规 |
启动和关闭调用的顺序非常重要。如果在两个对象之间存在“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
的任何“normal
”Lifecycle
对象的默认 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 的 |
如果你在非 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
集成涉及运行时可变状态,例如必须声明为 volatile
的 runnable
字段。虽然公共生命周期回调遵循特定顺序(例如,保证只有在完全初始化后才发生启动回调,并且只有在初始启动后才发生停止回调),但有一个特殊情况是销毁之前的常用停止安排:强烈建议这种情况下的任何 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 constructor
和 byType`自动装配模式(如 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
除了 ApplicationContextAware
和 BeanNameAware
(见 earlier),Spring 提供了广泛的 Aware
回调接口,这些接口允许 Bean 指示容器它们需要特定的基础架构依赖性。作为一般规则,名称表示依赖类型。下表总结了最重要的 Aware
接口:
Name | Injected Dependency | Explained in… |
---|---|---|
|
Declaring |
|
|
|
|
|
用于加载 Bean 类的类加载器。 |
|
|
Declaring |
|
|
声明 Bean 的名称。 |
|
|
用于在加载时处理类定义的已定义织入器。 |
|
|
针对消息解析(支持参数化和国际化)配置的策略。 |
|
|
Spring JMX notification publisher. |
|
|
为资源低级访问配置加载器。 |
|
|
容器目前`ServletConfig`运行在其中。仅在 web 兼容的 Spring `ApplicationContext`中有效。 |
|
|
容器目前`ServletContext`运行在其中。仅在 web 兼容的 Spring `ApplicationContext`中有效。 |
请再次注意,使用这些接口会将您的代码绑定到 Spring API,且不遵循控制反转风格。因此,我们建议将它们用于需要对容器进行编程访问的基础架构 bean。