|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
自定义 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 在容器为其设置完所有必要属性之后执行初始化工作。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 还支持推断销毁方法,能够自动检测 public 的 close 或 shutdown 方法。这是 Java 配置类中 @Bean 方法的默认行为,并会自动匹配实现了 java.lang.AutoCloseable 或 java.io.Closeable 接口的类,从而避免将销毁逻辑与 Spring 耦合。
对于使用 XML 进行销毁方法推断,您可以将 <bean> 元素的 destroy-method 属性赋予一个特殊的 (inferred) 值,这将指示 Spring 自动检测特定 Bean 定义在 Bean 类上的公共 close 或 shutdown 方法。
您也可以将此特殊的 (inferred) 值设置在 <beans> 元素的 default-destroy-method 属性上,以便将此行为应用于整组 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>
顶层 default-init-method 元素上的 <beans/> 属性的存在,
会使 Spring IoC 容器将 bean 类中名为 init 的方法识别为初始化方法回调。
当 bean 被创建并装配完成后,如果该 bean 类包含这样的方法,
它将在适当的时机被调用。
你可以通过在顶层 default-destroy-method 元素上使用 <beans/> 属性,以类似的方式(即在 XML 中)配置销毁方法回调。
如果现有的 bean 类已经包含与约定命名不一致的回调方法,你可以通过在 init-method 元素本身中使用 destroy-method 和 <bean/> 属性(即在 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注解的方法 -
destroy()方法,由DisposableBean回调接口定义 -
一个自定义配置的
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 接口提供了另一种选择,即其父接口 getPhase() 中定义的 Phased 方法。以下代码清单展示了 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 的对象,将会在启动时最早启动,在停止时最晚停止。在相位值范围的另一端,相位值为 Integer.MAX_VALUE 表示该对象应当最后启动、最先停止(很可能是因为它依赖于其他进程先运行起来)。在考虑相位值时,还需注意:任何未实现 Lifecycle 接口的普通 SmartLifecycle 对象,默认相位值为 0。因此,任何负的相位值都表示该对象应在这些标准组件之前启动(并在它们之后停止),而任何正的相位值则正好相反。
SmartLifecycle 接口中定义的 run() 方法接受一个回调参数。任何实现类都必须在其自身的关闭流程完成后调用该回调的 LifecycleProcessor 方法。这在必要时可实现异步关闭,因为 DefaultLifecycleProcessor 接口的默认实现类 lifecycleProcessor 会等待每个阶段中的对象组调用该回调,最长等待时间为其设定的超时值。默认情况下,每个阶段的超时时间为 30 秒。您可以通过在上下文中定义一个名为 5 的 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 注册一个关闭钩子(shutdown hook)。这样做可以确保应用优雅地关闭,并调用你的单例 Bean 上相应的销毁方法,从而释放所有资源。你仍然必须正确地配置和实现这些销毁回调。
要注册一个关闭钩子,请调用 registerShutdownHook() 接口中声明的 ConfigurableApplicationContext 方法,如下例所示:
-
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...
}
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-method)之前被调用。
其他Aware接口
除了 ApplicationContextAware 和 BeanNameAware(前面已讨论过)之外,
Spring 还提供了多种 Aware 回调接口,允许 Bean 向容器表明它们需要某种基础设施依赖。
通常来说,接口名称就表明了其所依赖的类型。下表总结了最重要的 Aware 接口:
| 姓名 | 注入的依赖 | 详见…… |
|---|---|---|
|
声明 |
|
|
封闭的 |
|
|
用于加载 Bean 类的类加载器。 |
|
|
声明 |
|
|
声明该 bean 的 bean 名称。 |
|
|
定义用于在加载时处理类定义的织入器。 |
|
|
用于解析消息的已配置策略(支持参数化和国际化)。 |
|
|
Spring JMX 通知发布器。 |
|
|
用于底层资源访问的已配置加载器。 |
|
|
当前容器所运行的 |
|
|
当前容器所运行的 |
再次注意,使用这些接口会将您的代码与 Spring API 耦合,并且不符合控制反转(Inversion of Control)的风格。因此,我们建议仅在需要以编程方式访问容器的基础架构 bean 中使用它们。