目录

1、初始化回调

2、销毁方法回调

2、默认的初始化和销毁方法

3、生命周期回调方法的执行顺序

4、启动和关闭的回调方法:Lifecycle 接口

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


        Spring 框架提供了许多接口,你可以使用这些接口自定义bean的性质。

实现 InitializingBean 和 DisposableBean 接口与管理 bean 生命周期的容器进行交互。容器可以在 bean 初始化之前调用 afterPropertiesSet() 方法,在 bean 销毁之后调用 destroy() 方法,来执行一些操作。// 第一种方式

使用 Java JSR-250 规范中的 @PostConstruct 和 @PreDestroy 注解通常被认为是 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解,意味着你的 bean 没有与特定的 Spring 接口进行耦合。两个注解的详细信息请参考这里。// 第二种方式

使用 bean 定义中的 init-method 和 destroy-method 属性。// 第三种方式

        在 Spring 框架内部,Spring 通过对 BeanPostProcessor 接口的方法实现来处理 bean 的接口回调。如果你需要定制一些 bean 的特性或者添加一些 Spring 没有提供的 bean 生命周期行为,你可以自己实现 BeanPostProcessor 接口,如果想要了解更多信息,请点击这里。// 第四种方式

Lifecycle 接口。所以,这些被 Spring 管理的对象能够参与到容器的启动和关闭的过程中来。// 容器和 Lifecycle 接口

1、初始化回调

接口允许 bean 在 Spring 容器设置了 bean 所有必须属性后,再执行初始化工作。InitializingBean 接口中定义了一个方法

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

Spring 官方并不推荐直接使用 InitializingBean 接口,因为没有必要把代码和 Spring 进行耦合。Spring 推荐使用 Java JSR-250 规范中的 @PostConstruct 注解或者使用 bean 定义中的初始化回调方法。在 bean 的 xml 配置中,你可使用 init-method 属性指定一个无参方法的方法名来实现初始化回调,如果使用的是 Java 配置方式,可以使用 @Bean 注解中的 initMethod 属性来指定,具体代码和详细步骤请点击这里。接下来,看一下下边的例子:

<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 耦合。

2、销毁方法回调

        org.springframework.beans.factory.DisposableBean 接口允许 bean 在容器中被销毁后再进行回调,DisposableBean 接口中定义了一个方法:

public interface DisposableBean {
    void destroy() throws Exception;
}

 @PreDestroy 注解或者使用 bean 定义中的销毁回调方法。 在 bean 的 xml 配置中,你可使用 destroy-method 属性指定一个销毁回调方法,如果使用的是 Java 配置方式,可以使用 @Bean 注解中的 destroyMethod 属性来指定,具体代码和详细步骤请点击这里。接下来,看一下下边的例子:

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

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

        上面的例子与下面的例子几乎具有完全相同的效果:

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

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

        但是,第一个示例中的代码并没有与 Spring 耦合。

// 默认行为:init() 为初始化回调方法

2、默认的初始化和销毁方法

        当你不使用 Spring 的 InitializingBean 和 DisposableBean 回调接口编写 initialization 或 destroy 回调方法时,你通常会使用  init(),initialize(),dispose() 等方法来命名回调接口。但理想情况下,对这些生命周期的回调方法的命名应该进行标准化,从而使得在一个项目中都使用相同的,一致的方法名。

// 统一回调方法命名

        建议初始化回调方法使用 init() 命名,销毁回调方法使用 destroy() 命名,就像下边的例子一样:

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("The [blogDao] property must be set.");
        }
    }
}

// 批量定义

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

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

        在顶层的  <beans/> 标签中配置 default-init-method 属性,使 Spring 容器能够将 class 中的 init() 的方法识别为初始化回调方法。当一个 bean 被创建后,如果它的 class 中有 init() 方法,Spring 会在恰当的时候自动调用它。定义销毁回调方法跟上边步骤一样。

// 自定义方法命名

当一个 bean 被完全创建后,该 bean 的 AOP 代理的拦截器链才会被执行。如果一个 bean 和它的代理被分开定义,那么你甚至能绕过代理直接与原生 bean 进行交互。因此,Spring 容器的初始化回调跟拦截应用到 init() 方法是不一致的,因为这样做,会把 bean 的生命周期跟它的代理或者拦截器耦合在一起,并在直接与原生 bean 交互时产生奇怪的语义。// 这段翻译得不是很顺,大意就是不要把 bean 中的生命周期方法跟拦截器耦合在一起

3、生命周期回调方法的执行顺序

在 Spring 中,你可以通过以下三点来控制生命周期的行为:

  1. InitializingBean 和 DisposableBean 回调接口
  2. 自定义 init() 和 destroy() 方法
  3. @PostConstruct 和 @PreDestroy 注解

如果为多个生命周期机制配置了相同的方法名称,例如,所有机制的初始化方法都为 init() ,那么该方法只会被执行一次。

        为同一个 bean 配置多个生命周期回调机制,且每个机制都配置了不同的方法名的情况下,方法的执行顺序如下:

  1. 执行 @PostConstruct 注解标记的方法
  2. 执行 InitializingBean 回调接口中的 afterPropertiesSet() 方法
  3. 执行自定义的  init() 方法

同样情况下,销毁回调方法的顺序如下:

  1. 执行 @PreDestroy 注解标记的方法
  2. 执行 DisposableBean 回调接口中的 destroy() 方法
  3. 执行自定义的 destroy() 方法

4、启动和关闭的回调方法:Lifecycle 接口

// 定义顺序,很重要的机制

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

当 ApplicationContext 容器接收到启动或者停止信号时,它会调用容器中定义的所有 Lifecycle 接口的实现方法。容器通过 LifecycleProcessor 接口来实现这些调用,LifecycleProcessor 接口如下边所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

        注意,LifecycleProcessor 接口是对 Lifecycle 接口的扩展,它额外提供了两个方法来响应上下文的刷新和关闭。

// 在 Spring 中,上下文就是指容器

实现了 Lifecycle 接口的 bean 会在执行销毁回调方法之前收到停止通知。然而,在上下文生命周期中的热刷新或停止刷新尝试时,只会调用 destroy() 方法。// 最后这句难以理解,什么是 Hot refresh?

        启动和关闭的调用顺序是非常重要的。如果两个对象之间存在依赖关系,被依赖的 bean 会在依赖此对象的 bean 之前先启动,关闭的顺序正好相反。但是,有时候,你并不能直接知道 Objects 之间的依赖关系。你可能只知道某一类型的对象应该先于另一类型的对象启动。在这种情况下 SmartLifecycle 接口提供了另一个选择:getPhase() 方法,该方法由 SmartLifecycle 的父接口 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(最小值 -2147483648),那么该对象将是第一个启动和最后一个停止的。另一方面,如果 phase 值为 Integer.MAX_VALUE,那么该对象是最后启动并首先停止的。另外,还需要考虑到没有实现 SmartLifecycle 接口而只是实现了 Lifecycle 接口的常规对象,这些对象的 phase 值默认为 0。因此,这意味着 phase 值为负的对象会在常规组件启动之前启动,在常规组件停止后停止,phase 值为正的情况刚好相反。// 非常重要,定义了 bean 之间的启动顺序。有没有顿悟?哈哈哈...

SmartLifecycle 接口中定义的 stop 方法接受回调。任何实现类都必须在该实现类关闭过程完成后,调用该回调的 run() 方法。这在必要的时候能够支持异步关闭,DefaultLifecycleProcessor 是 LifecycleProcessor 接口的默认实现 ,它会在每个阶段都等待一个对象的超时值,然后再去调用回调方法。每个阶段的超时值默认为 30 秒。当然,你可以自定义生命周期处理器,只要在上下文中定义一个名为 lifecycleProcessor 的 bean 就可以了,如果你只想修改超时时间,那么,下边的代码就足够了: // 自定义生命周期处理器,修改对象回调的超时时间

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

LifecycleProcessor 接口还提供了刷新(onRefresh)和关闭容器(onClose)的回调方法。调用 onClose() 时,就像显式调用了 stop() 一样,但是它发生在容器关闭的过程中。另一方面,'refresh' 回调开启了 SmartLifecycle bean 的另一个特性。当刷新容器时(此时,所有对象都已实例化和初始化),将调用 onRefresh 回调方法。在这个时间点上,默认的生命周期处理器会检查每个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值,如果为 true,该对象将在此时启动,而不是等待容器或自己的 start() 方法的显示调用(与刷新容器不同,对于标准容器实现,启动容器不会自动发生)。如前面所述,phase 值和依赖关系(“depends-on” relationships)决定对象的启动顺序。// 刷新容器时调用 onRefresh 方法,容器不会自动启动,但是会自动刷新

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

        基于 web 的 Spring ApplicationContext 容器,在关闭 web 应用程序时,已经有了优雅关闭 Spring IoC 容器的实现。

向 JVM 注册一个关闭的钩子(shutdown hook)。这样做,可以确保容器被优雅的关闭,并且能够通过调用单例 bean 上相关的 destroy 方法,释放所有的资源。但是,你必须要正确地配置和实现这些销毁回调方法。// 注册关闭钩子 shutdown hook

       调用 ConfigurableApplicationContext 接口当中的 registerShutdownHook() 方法,可以注册一个 shutdown hook,代码如下所示:

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

        // 为上面的容器添加一个shutdown钩子...
        ctx.registerShutdownHook();

        // app runs here...

        // Main方法退出,钩子在应用程序关闭之前被调用…
    }
}

        至此,如何在非 web 应用程序中优雅地关闭 Spring IoC 容器的方法介绍完毕。