跳到主要内容
版本:7.0.3

自定义Bean的属性

Hunyuan 7b 中英对照 Customizing the Nature of a Bean

Spring框架提供了一系列接口,您可以使用这些接口来自定义bean的属性。本节将这些接口分类如下:

生命周期回调

要与容器对bean生命周期的管理进行交互,你可以实现Spring的 InitializingBeanDisposableBean接口。容器会调用前者的afterPropertiesSet()方法,以及后者的destroy()方法,以便让bean在初始化和销毁时执行某些操作。

提示

在现代的Spring应用程序中,JSR-250中的@PostConstruct@PreDestroy注解通常被视为接收生命周期回调的最佳实践。使用这些注解意味着你的Bean不会与特定于Spring的接口耦合。详情请参见使用@PostConstruct和@PreDestroy

如果你不想使用JSR-250注解,但仍希望消除耦合,可以考虑使用init-methoddestroy-method作为Bean定义的元数据。

在内部,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配置时,您可以使用@BeaninitMethod属性。请参阅接收生命周期回调。请考虑以下示例:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

public void init() {
// do some initialization work
}
}

前面的例子与下面的例子(由两个列表组成)产生的效果几乎完全相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

@Override
public void afterPropertiesSet() {
// do some initialization work
}
}

然而,前面两个例子中的第一个并没有将代码与Spring耦合起来。

备注

请注意,@PostConstruct方法以及一般的初始化方法都是在容器的单例创建锁(singleton creation lock)生效期间执行的。只有当@PostConstruct方法执行完成后,该Bean实例才被视为完全初始化完毕,可以对外部使用。这些初始化方法的主要用途是验证配置状态,并根据给定的配置准备一些数据结构,但不应再包含对外部Bean的访问操作。否则,就有可能导致初始化死锁(initialization deadlock)。

如果需要在初始化后执行耗时的操作(例如异步的数据库准备步骤),那么你的Bean应该实现SmartInitializingSingleton.afterSingletonsInstantiated()方法,或者依赖于上下文刷新事件:实现ApplicationListener<ContextRefreshedEvent>接口,或者声明相应的注解@EventListener(ContextRefreshedEvent.class)。这些操作会在所有常规的单例初始化之后执行,因此不会受到单例创建锁的限制。

另外,你也可以实现(Smart)Lifecycle接口,从而与容器的整体生命周期管理机制集成,包括自动启动机制、预销毁停止步骤,以及可能的停止/重启回调(详见下文)。

销毁回调

实现org.springframework.beans.factory.DisposableBean接口可以让bean在包含它的容器被销毁时收到一个回调。DisposableBean接口指定了一个方法:

void destroy() throws Exception;

我们建议不要使用DisposableBean回调接口,因为它不必要地将代码与Spring耦合在一起。作为替代,我们建议使用@PreDestroy注解,或者指定一个被bean定义支持的泛型方法。在基于XML的配置元数据中,你可以在<bean/>上使用destroy-method属性。在Java配置中,你可以使用@BeandestroyMethod属性。请参阅接收生命周期回调。考虑以下定义:

<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}

前面的定义与以下定义几乎具有完全相同的效果:

<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}

然而,前面两个定义中的第一个并没有将代码与Spring关联起来。

请注意,Spring还支持销毁方法的推断,可以检测到公开的closeshutdown方法。这是Java配置类中@Bean方法的默认行为,它会自动匹配java.lang.AutoCloseablejava.io.Closeable的实现,也不会将销毁逻辑与Spring耦合起来。

提示

对于使用XML进行销毁方法推断,您可以为<bean>元素分配一个特殊的(inferred)值给destroy-method属性,这样就可以指示Spring自动检测特定bean定义的bean类中的公共closeshutdown方法。您也可以在<beans>元素的default-destroy-method属性上设置这个特殊的(inferred)值,以便将此行为应用到整个bean定义集合上(请参见默认初始化和销毁方法)。

备注

对于延长的关闭阶段,你可以实现Lifecycle接口,在任何单例bean的destroy方法被调用之前接收到一个提前停止信号。你也可以实现SmartLifecycle,以便在有一个时间限制的停止步骤中,容器会等待所有此类停止处理完成后再继续执行destroy方法。

默认初始化和销毁方法

当你编写不使用Spring特有的InitializingBeanDisposableBean回调接口的初始化和销毁方法回调时,通常会编写名为init()initialize()dispose()等方法。理想情况下,这类生命周期回调方法的名称应在整个项目中保持标准化,以便所有开发者都能使用相同的方法名,从而确保一致性。

你可以配置Spring容器在每个bean上“查找”具有名称的初始化和销毁回调方法。这意味着,作为应用程序开发者,你可以编写自己的应用程序类,并使用名为init()的初始化回调方法,而无需在每个bean定义中都配置init-method="init"属性。当bean被创建时,Spring IoC容器会调用该方法(并且是按照之前描述的标准生命周期回调契约来执行的[#beans-factory-lifecycle])。这一特性还强制要求初始化和销毁方法回调遵循一致的命名规范。

假设你的初始化回调方法名为init(),销毁回调方法名为destroy()。那么你的类就类似于以下示例中的类:

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.");
}
}
}

然后你可以在一个类似于以下的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-methoddestroy-method属性来覆盖默认值,即指定方法名称。

Spring容器保证,在Bean获取到所有依赖项后,会立即调用配置好的初始化回调方法。因此,初始化回调是在原始Bean引用上被调用的,这意味着AOP拦截器等尚未应用于该Bean。首先会完全创建目标Bean,然后才会应用带有拦截器链的AOP代理(例如)。如果目标Bean和代理是分开定义的,那么你的代码甚至可以直接与原始目标Bean进行交互,绕过代理。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标Bean的生命周期与其代理或拦截器绑定在一起,当你的代码直接与原始目标Bean交互时,会导致语义上的混乱。

结合生命周期机制

从 Spring 2.5 开始,你有三种选项来控制 Bean 的生命周期行为:

备注

如果为一个bean配置了多种生命周期机制,并且每种机制都配置了不同的方法名,那么每次运行时都会按照本注释后列出的顺序执行这些方法。然而,如果为多种生命周期机制配置了相同的方法名(例如,初始化方法使用init()),那么该方法只会被执行一次,具体说明请参见前一节

为同一个bean配置了多种生命周期机制,并且这些机制具有不同的初始化方法,这种情况的称呼如下:

  1. @PostConstruct 注解的方法

  2. afterPropertiesSet(),由 InitializingBean 回调接口定义

  3. 自定义配置的 init() 方法

销毁方法的调用顺序是相同的:

  1. @PreDestroy注解的方法

  2. DisposableBean回调接口定义的destroy()方法

  3. 自定义配置的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 接口的扩展。它还添加了另外两个方法,用于响应上下文的刷新和关闭。

提示

请注意,普通的 org.springframework.context.Lifecycle 接口只是一个用于显式启动和停止通知的简单契约,并不意味着在上下文刷新时会自动启动。为了对自动启动进行细粒度控制,以及优雅地停止某个bean(包括启动和停止阶段),可以考虑实现扩展的 org.springframework.context.SmartLifecycle 接口。

另外,请注意,停止通知并不保证会在销毁之前发出。在常规关闭过程中,所有 Lifecycle bean 在通用销毁回调被触发之前会先收到停止通知。然而,在上下文生命周期内的热刷新或停止刷新尝试时,只会调用销毁方法。

启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在“依赖关系”,那么依赖方会在其依赖对象之后启动,并在其依赖对象之前停止。然而,有时直接的依赖关系是未知的。你可能只知道某种类型的对象应该在其他类型的对象之前启动。在这种情况下,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定义的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容器 ({#beans-factory-shutdown})

备注

本节仅适用于非Web应用程序。Spring基于Web的ApplicationContext实现已经具备在相关Web应用程序关闭时优雅地关闭Spring IoC容器的代码。

如果你在非Web应用程序环境中使用Spring的IoC容器(例如,在富客户端桌面环境中),则需要向JVM注册一个关闭钩子。这样做可以确保应用程序能够优雅地关闭,并调用你的单例bean上的相关销毁方法,从而释放所有资源。你仍然必须正确配置和实现这些销毁回调。

要注册一个关闭钩子(shutdown hook),请调用在 ConfigurableApplicationContext 接口上声明的 registerShutdownHook() 方法,如下例所示:

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...
}
}

线程安全与可见性

Spring核心容器以线程安全的方式发布创建的单例实例,通过单例锁来保护访问,并保证在其他线程中也能看到这些单例实例。

因此,应用程序提供的Bean类无需关注其初始化状态的可见性。只要配置字段仅在初始化阶段被修改,就不需要将其标记为volatile,这样就可以为在初始化阶段内可修改的、基于setter的配置状态提供与final类似的可见性保证。如果这些字段在Bean创建阶段及其随后的初始发布之后被修改,则在每次访问时都需要将它们声明为volatile或通过互斥锁来保护。

请注意,在容器端进行了安全的初始发布之后,对于单例bean实例(例如控制器实例或仓库实例)中的此类配置状态的并发访问是完全线程安全的。这包括在通用单例锁内处理的常见单例FactoryBean实例。

对于销毁回调(destruction callbacks),配置状态(configuration state)仍然是线程安全的,但根据Java的通用指南,从初始化到销毁期间积累的任何运行时状态(runtime state)应该保存在线程安全的结构中(对于简单情况,也可以保存在volatile字段中)。

如上所示,更深层次的Lifecycle集成涉及运行时可变状态,例如runnable字段,该字段必须被声明为volatile。虽然常见的生命周期回调遵循一定的顺序,例如,启动回调确保只在完全初始化后才会发生,而停止回调则只在初次启动之后才会发生,但对于“先停止再销毁”的常见流程存在一个特例:强烈建议此类bean的内部状态也允许在没有任何先前的停止操作的情况下立即触发销毁回调,因为这种情况可能发生在取消引导过程后的异常关闭中,或者在由另一个bean引起的停止超时情况下。

ApplicationContextAwareBeanNameAware

当一个ApplicationContext创建了一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,该实例会获得对该ApplicationContext的引用。以下列表显示了ApplicationContextAware接口的定义:

public interface ApplicationContextAware {

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,beans可以通过ApplicationContext接口,或者通过将引用强制转换为该接口的已知子类(例如ConfigurableApplicationContext,它提供了额外的功能)来以编程方式操作创建它们的ApplicationContext。一种用途是以编程方式检索其他beans。有时这种能力非常有用。然而,一般来说,你应该避免这样做,因为它会将代码与Spring耦合起来,并且不符合控制反转(Inversion of Control)的设计风格,在控制反转风格中,协作对象是通过props提供给beans的。ApplicationContext的其他方法还提供了对文件资源的访问、发布应用事件以及访问MessageSource的功能。这些额外功能在ApplicationContext的附加功能中有描述。

自动注入(Autowiring)是获取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 接口

除了ApplicationContextAwareBeanNameAware(在前面中讨论过),Spring还提供了多种Aware回调接口,这些接口允许bean向容器表明它们需要某种基础设施依赖。一般来说,接口的名称就代表了依赖的类型。下表总结了最重要的Aware接口:

表1. 意识接口

名称注入的依赖说明见于……
ApplicationContextAware声明 ApplicationContextApplicationContextAware 和 BeanNameAware
ApplicationEventPublisherAware所在 ApplicationContext 的事件发布器。ApplicationContext 的附加功能
BeanClassLoaderAware用于加载 bean 类的类加载器。实例化 Bean
BeanFactoryAware声明 BeanFactoryBeanFactory API
BeanNameAware声明 bean 的名称。ApplicationContextAware 和 BeanNameAware
LoadTimeWeaverAware用于在加载时处理类定义的织入器(weaver)。Spring 框架中的加载时织入(Load-time Weaving)
MessageSourceAware配置的消息解析策略(支持参数化和国际化)。ApplicationContext 的附加功能
NotificationPublisherAwareSpring JMX 通知发布器。通知(Notifications)
ResourceLoaderAware配置的用于低级资源访问的加载器。资源(Resources)
ServletConfigAware容器当前运行的 ServletConfig。仅适用于支持 Web 的 Spring ApplicationContextSpring MVC
ServletContextAware容器当前运行的 ServletContext。仅适用于支持 Web 的 Spring ApplicationContextSpring MVC

请再次注意,使用这些接口会将你的代码与Spring API绑定起来,并且不符合控制反转(Inversion of Control)的设计风格。因此,我们建议将它们用于那些需要程序化方式访问容器的基础设施bean(infrastructure beans)。