Customizing the Nature of a Bean
The Spring Framework provides a number of interfaces you can use to customize the nature of a bean. This section groups them as follows:
生命周期回调
要与容器的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() {
// 进行一些初始化工作
}
}
class ExampleBean {
fun init() {
// 进行一些初始化工作
}
}
前面的示例几乎与以下示例具有相同的效果(包含两个列表):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
-
Java
-
Kotlin
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// 进行一些初始化工作
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// 进行一些初始化工作
}
}
然而,前面两个示例中的第一个不会将代码与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() {
// 进行一些销毁工作(如释放连接池连接)
}
}
class ExampleBean {
fun cleanup() {
// 进行一些销毁工作(如释放连接池连接)
}
}
前面的定义几乎与以下定义具有相同的效果:
<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
-
Java
-
Kotlin
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// 进行一些销毁工作(如释放连接池连接)
}
}
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// 进行一些销毁工作(如释放连接池连接)
}
}
然而,前面两个定义中的第一个不会将代码与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;
}
// 这是(不出所料的)初始化回调方法
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("必须设置[blogDao]属性。");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// 这是(不出所料的)初始化回调方法
fun init() {
if (blogDao == null) {
throw IllegalStateException("必须设置[blogDao]属性。")
}
}
}
然后,您可以在类似以下的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的生命周期行为:
-
自定义
init()
和destroy()
方法 -
-
您可以结合这些机制来控制给定的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);
}
启动时,具有最低阶段的对象首先启动。停止时,按相反顺序进行。因此,实现SmartLifecycle
接口且其getPhase()
方法返回Integer.MIN_VALUE
的对象将首先启动并最后停止。在光谱的另一端,阶段值为Integer.MAX_VALUE
表示该对象应该最后启动并首先停止(可能是因为它依赖于其他进程正在运行)。在考虑阶段值时,还很重要知道,任何未实现SmartLifecycle
的“正常”Lifecycle
对象的默认阶段为0
。因此,任何负阶段值表示对象应在这些标准组件之前启动(并在它们之后停止)。对于任何正阶段值,情况相反。
SmartLifecycle
定义的停止方法接受一个回调。任何实现必须在该实现的关闭过程完成后调用该回调的run()
方法。这使得在必要时进行异步关闭成为可能,因为LifecycleProcessor
接口的默认实现DefaultLifecycleProcessor
会等待每个阶段内的对象组调用该回调的超时值。默认每个阶段的超时时间为30秒。您可以通过在上下文中定义一个名为lifecycleProcessor
的bean来覆盖默认的生命周期处理器实例。如果只想修改超时时间,则定义以下内容即可:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- 超时值(毫秒) -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor
接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程,就好像显式调用了stop()
一样,但是发生在上下文关闭时。另一方面,“刷新”回调使SmartLifecycle
bean的另一个功能生效。当上下文刷新时(在所有对象都已实例化和初始化之后),将调用该回调。此时,默认生命周期处理器会检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的布尔值。如果为true
,则该对象在此时启动,而不是等待上下文或其自身的start()
方法的显式调用(与上下文刷新不同,标准上下文实现不会自动进行上下文启动)。阶段值和任何“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");
// 为上述上下文添加一个关闭挂钩...
ctx.registerShutdownHook();
// 应用程序在此运行...
// 主方法退出前,挂钩将在应用程序关闭之前被调用...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// 为上述上下文添加一个关闭挂钩...
ctx.registerShutdownHook()
// 应用程序在此运行...
// 主方法退出前,挂钩将在应用程序关闭之前被调用...
}
线程安全和可见性
Spring核心容器以线程安全的方式发布创建的单例实例,通过单例锁保护访问,并确保在其他线程中可见。
因此,应用程序提供的bean类无需关注其初始化状态的可见性。只要在初始化阶段仅对常规配置字段进行变异,常规配置字段就无需标记为volatile
,即使对于在初始阶段可变的基于setter的配置状态,也提供类似于final
的可见性保证。如果这些字段在bean创建阶段及其后的初始发布之后发生更改,则需要将其声明为volatile
或在访问时由公共锁保护。
请注意,在单例bean实例中对这种配置状态的并发访问(例如对控制器实例或存储库实例)在从容器方面进行安全初始发布后是完全线程安全的。这还包括在一般单例锁内处理的常见单例FactoryBean
实例。
对于销毁回调,配置状态仍然是线程安全的,但在初始化和销毁之间累积的任何运行时状态应保留在线程安全结构中(或对于简单情况,保留在volatile
字段中),按照常见的Java指南。
如上所示,更深入的Lifecycle
集成涉及到可运行字段等运行时可变状态,这些状态必须声明为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
的引用的另一种选择。传统的constructor
和byType
自动装配模式(如自动装配协作者中所述)可以为构造函数参数或setter方法参数提供类型为ApplicationContext
的依赖项。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,可以使用基于注解的自动装配功能。如果这样做,如果字段、构造函数或方法携带@Autowired
注解,则期望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的名称。 |
|
|
用于在加载时处理类定义的定义编织器。 |
|
|
用于解析消息的配置策略(支持参数化和国际化)。 |
|
|
Spring JMX通知发布者。 |
|
|
用于低级访问资源的配置加载器。 |
|
|
容器运行的当前 |
|
|
容器运行的当前 |
再次注意,使用这些接口将您的代码与Spring API绑定在一起,不符合控制反转的风格。因此,我们建议将它们用于需要以编程方式访问容器的基础设施bean。