Spring 自定义 bean 的生命周期

Posted carl-zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 自定义 bean 的生命周期相关的知识,希望对你有一定的参考价值。

1、Lifecycle 回调

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

在内部,Spring 框架使用 BeanPostProcessor 实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要自定义特性或 Spring 没有提供的其他开箱即用的生命周期行为,您可以自己实现BeanPostProcessor

除了初始化和销毁回调,spring 管理的对象还可以实现 Lifecycle 接口,以便这些对象可以参与由容器自己的生命周期驱动的启动和关闭过程。

本节将描述生命周期回调接口。

1.1 初始化回调

org.springframework.beans.factory.InitializingBean 接口允许bean在容器设置了bean上的所有必要属性之后执行初始化工作。InitializingBean接口指定了一个方法:

void afterPropertiesSet() throws Exception;

建议您不要使用 InitializingBean 接口,因为它不必要地将代码与 Spring 耦合。或者,使用 @PostConstruct 注释或指定 POJO 初始化方法。对于基于 xml 的配置元数据,可以使用 init-method 属性指定具有空无参数签名的方法名称。使用 Java 配置,您可以使用 @BeaninitMethod 属性。例如:

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

    public void init() 
        // do some initialization work
    

和以下的代码完全一样,但没有将代码与 Spring 耦合。

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

    public void afterPropertiesSet() 
        // do some initialization work
    

1.2 销毁回调

实现 org.springframework.beans.factory.DisposableBean 接口允许bean在容器被销毁时获得一个回调。DisposableBean 接口指定了一个方法:

void destroy() throws Exception;

建议您不要使用 DisposableBean 回调接口,因为它不必要地将代码与 Spring 耦合在一起。或者,使用 @PreDestroy 注释或指定bean定义支持的泛型方法。对于基于 xml 的配置元数据,可以在 <bean/>上使用destroy-method 属性。使用 Java 配置,您可以使用 @BeandestroyMethod属性。例如下面的定义:

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

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

和以下的代码完全一样,但没有将代码与 Spring 耦合。

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

    public void destroy() 
        // do some destruction work (like releasing pooled connections)
    

<bean>元素的 destroy-method 属性可以被赋一个特殊的(推断的)值,该值指示 Spring 自动检测特定bean 类上的 close 或者 shutdown (因此任何实现 java.lang.AutoCloseable java.io.Closeable 的类都会匹配)。还可以在<beans>元素的 Default -destroy-method 属性上设置这个特殊的(推断出来的)值,以便将此行为应用于整个 bean 集。注意,这是 Java 配置的默认行为。

1.3 默认的实例和销毁方法

当您编写不使用 spring 特定的 InitializingBean DisposableBean 回调接口的初始化和销毁方法回调时,您通常会编写具有 init() initialize() dispose() 等名称的方法。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

您可以配置 Spring 容器来查找每个 bean 上的命名初始化并销毁回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为 init() 的初始化回调,而不必为每个bean定义配置 init-method="init" 属性。Spring IoC 容器在创建 bean 时调用该方法(并且按照前面描述的标准生命周期回调契约)。这个特性还强制对初始化和销毁方法回调使用一致的命名约定。

假设初始化回调方法命名为 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.");
        
    

<beans default-init-method="init">

    <bean id="blogService" class="com.foo.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 交互时留下奇怪的语义。

1.4 组合生命周期机制

在Spring 2.5中,你有三个选项来控制bean的生命周期行为: InitializingBean DisposableBean 回调接口;自定义 init() destroy() 方法; @PostConstruct @PreDestroy 注解。您可以组合这些机制来控制给定的 bean。

如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法将按照下面列出的顺序执行。但是,如果为这些生命周期机制中的多个配置了相同的方法名——例如,初始化方法的 init() —— 那么该方法将执行一次,如上一节所述。

为同一个 bean 配置的多个生命周期机制,使用不同的初始化方法,调用如下:

  • @PostConstruct 注释的方法
  • InitializingBean 回调接口定义的 afterPropertiesSet()
  • 一个自定义配置的 init() 方法

Destroy 方法的调用顺序相同:

  • @PreDestroy 注释的方法
  • destroy() 函数由 DisposableBean 回调接口定义
  • 一个自定义配置的 destroy() 方法

1.5 启动和关闭回调

Lifecycle 接口定义了任何有自己生命周期需求的对象的基本方法(例如启动和停止一些后台进程):

public interface Lifecycle 

    void start();

    void stop();

    boolean isRunning();

任何 spring 管理的对象都可以实现这个接口。然后,当 ApplicationContext 本身接收到启动和停止信号时,例如运行时的停止/重启场景,它将把这些调用级联到在该上下文中定义的所有 Lifecycle 实现。它通过委托给LifecycleProcessor 来实现:

public interface LifecycleProcessor extends Lifecycle 

    void onRefresh();

    void onClose();

注意,LifecycleProcessor 本身就是 Lifecycle 接口的扩展。它还添加了另外两个方法来响应正在刷新和关闭的上下文。

注意,常规的 org.springframework.context.Lifecycle 接口只是一个显式启动/停止通知的普通约定,并不意味着在上下文刷新时自动启动。考虑实现 org.springframework.context.SmartLifecycle 来代替对特定 bean 的自动启动进行细粒度的控制(包括启动阶段)。另外,请注意,停止通知不保证在销毁之前出现:在常规的关闭中,所有 Lifecycle beans 将首先收到一个停止通知,然后一般销毁回调被传播;但是,在上下文生命周期内的热刷新或被中止的刷新尝试时,只会调用 destroy 方法。

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,依赖端将在其依赖项之后启动,并在其依赖项之前停止。然而,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle 接口定义了另一个选项,即在其超级接口 phaseage 上定义的 getPhase() 方法。

public interface Phased 

    int getPhase();

public interface SmartLifecycle extends Lifecycle, Phased 

    boolean isAutoStartup();

    void stop(Runnable callback);

当启动时,具有最低阶段的对象首先启动,当停止时,顺序相反。因此,实现 SmartLifecycle 并且getPhase() 方法返回 Integer.MIN_VALUE 是最先开始的,也是最后停止的。在频谱的另一端,一个整数的相位值 Integer.MIN_VALUE 表示应该最后启动对象,首先停止对象(可能是因为它依赖于正在运行的其他进程)。当考虑阶段值时,重要的是要知道,任何“正常的” Lifecycle 对象没有实现 SmartLifecycle 的默认阶段将是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() 方法的显式调用(与上下文刷新不同,对于标准的上下文实现,上下文启动不会自动发生)。“阶段”值以及任何“依赖”关系将按照上述相同的方式决定启动顺序。

1.6 在非web应用程序中优雅地关闭Spring IoC容器

本节只适用于非 web 应用。Spring 基于 web 的 ApplicationContext 实现已经有了适当的代码,可以在相关web 应用程序关闭时优雅地关闭 Spring IoC 容器。

如果您在非 web 应用程序环境中使用 Spring 的 IoC 容器;例如,在富客户机桌面环境中;你向 JVM 注册一个关闭钩子。这样做可以确保安全关闭,并调用单例 bean上的相关销毁方法,以便释放所有资源。当然,您仍然必须正确地配置和实现这些销毁回调。

要注册一个关机钩子,你需要调用在 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...
    

2、ApplicationContextAware 和 BeanNameAware

ApplicationContext 创建一个实现了 org.springframework.context.ApplicationContextAware 接口的对象实例时,该实例将被提供一个对该 ApplicationContext 的引用。

public interface ApplicationContextAware 

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

因此,bean可以通过 ApplicationContext 接口以编程方式操作创建它们的 ApplicationContext,或者通过将引用强制转换为该接口的已知子类,例如 ConfigurableApplicationContext,它公开了额外的功能。一种用途是对其他bean进行程序化检索。有时这种能力是有用的;但是,通常应该避免使用它,因为它将代码与Spring耦合在一起,并且不遵循控制反转风格,在这种风格中,协作者被作为属性提供给bean。ApplicationContext 的其他方法提供对文件资源的访问、发布应用程序事件和访问 MessageSource

在 Spring 2.5 中,自动装配是获取 ApplicationContext 引用的另一种选择。“传统” constructorbyType 自动装配模式可以分别为构造函数参数或 setter 方法参数提供一个 ApplicationContext 类型的依赖项。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用新的基于注释的自动装配特性。如果这样做,ApplicationContext 将被自动连接到字段、构造函数参数或方法参数中,如果有问题的字段、构造函数或方法携带 @Autowired 注释,则这些字段、构造函数参数或方法参数将期望 ApplicationContext 类型。

ApplicationContext 创建一个实现 org.springframework.bean .factory. BeanNameAware 接口的类时,会为该类提供一个对其关联对象定义中定义的名称的引用。

public interface BeanNameAware 

    void setBeanName(String name) throws BeansException;

在填充普通 bean 属性之后,但在初始化回调(如InitializingBeanafterPropertiesSet 或自定义初始化方法)之前调用回调。

3、其它 Aware 接口

除了上面讨论的 ApplicationContextAwareBeanNameAware 之外,Spring 还提供了一系列 Aware 接口,这些接口允许 bean 向容器指示它们需要特定的基础设施依赖项。下面总结了最重要的 Aware 接口—— 作为一个通用规则,名称是依赖类型的良好指示:

接口名称注入依赖
ApplicationContextAware声明 ApplicationContext
ApplicationEventPublisherAware包含 ApplicationContext 的事件发布者
BeanClassLoaderAware用于装入 bean 类的类装入器。
BeanFactoryAware声明 BeanFactory
BeanNameAware声明 bean 的名称
BootstrapContextAware容器运行在资源适配器 BootstrapContext 中。通常仅在 JCA 感知的应用程序上下文中可用
LoadTimeWeaverAware定义了用于在加载时处理类定义的编织器
MessageSourceAware已配置的消息解析策略(支持参数化和国际化)
NotificationPublisherAwareSpring JMX 通知发布者
PortletConfigAware容器运行的当前 PortletConfig。仅在 web 感知的 Spring ApplicationContext 中有效
PortletContextAware容器运行的当前 PortletContext。仅在 web 感知的 Spring ApplicationContext 中有效
ResourceLoaderAware已配置的用于低级访问资源的加载器
ServletConfigAware当前运行容器的 ServletConfig。仅在web感知的 Spring ApplicationContext 中有效
ServletContextAware当前运行容器的 ServletContext。仅在web感知的 Spring ApplicationContext 中有效

再次注意,这些接口的使用将您的代码与 Spring API 绑定在一起,并且不遵循控制反转样式。因此,对于需要对容器进行编程访问的基础结构bean,建议使用它们。

以上是关于Spring 自定义 bean 的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

Spring 自定义 bean 的生命周期

Spring-IOC学习笔记-06bean的生命周期

spring之bean的生命周期

Spring Bean的生命周期:

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.6 自定义Bean的特性 6.6.1 生命周期回调

几种自定义Spring生命周期的初始化和销毁方法