1.6 自定义bean的性质

Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将其分组如下:

  • 生命周期
  • ApplicationContextAware和 BeanNameAware
  • 其它Aware接口

1.6.1. 生命周期

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

JSR-250的@PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定的Spring的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。
如果您不想使用JSR-250注释,但仍然想删除耦合,那么考虑在配置里定义init方法并销毁方法的bean。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要定制特性或其他生命周期行为,那么Spring在默认情况下不提供,您可以自己实现beanPostprocessor。
除了初始化和销毁回调之外,Spring管理的对象还可以实现生命周期接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程。
生命周期回调接口在本节中进行了描述。
初始化
org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean的所有必要属性之后执行初始化工作。InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到spring。或者,我们建议使用InitializingBean注释或指定POJO初始化方法。对于基于XML的配置,可以使用init method属性指定具有void 无参数签名的方法的名称。使用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 {

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

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

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

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置,可以在<bean/>上使用destroy-method属性。使用Java配置,可以@Bean的estroyMethod属性,请参见接收生命周期回调。考虑以下定义:

<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 {

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

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

可以为<bean>元素的destroy-method 属性指定一个特殊(推断)值,该值指示spring自动检测特定bean类上的公共关闭或关闭方法。(因此,实现java.lang.AutoCloseablejava.io.Closeable的任何类都将起作用。)您还可以在<beans>元素的default-destroy-method属性上设置此特殊(推断)值,以将此行为应用于整个bean集。注意,这是Java配置的默认行为。

默认初始化和销毁方法
在编写不使用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.");
        }
    }
}

然后可以在类似于以下内容的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 属性,可以类似地配置destroy方法回调。
如果现有bean类已经具有与约定不一致的回调方法,你可以重写,并在<bean/>的 init-method 和 destroy-method 属性来指定方法。
Spring容器保证在向bean提供所有依赖项之后立即调用配置的初始化方法。因此,对原始bean调用初始化,这意味着AOP拦截器等尚未应用于bean。首先完全创建一个目标bean,然后应用一个带有拦截器链的AOP代理。如果目标bean和代理是单独定义的,那么您的代码甚至可以与原始目标bean交互,而不必使用代理。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。

结合生命周期机制
从Spring2.5开始,您有三个控制bean生命周期行为的选项:

  • InitializingBean 和 DisposableBean调用接口
  • 自定义init()和destroy()方法
  • @PostConstruct 和 @PreDestroy注解,您可以组合这些机制来控制给定的bean。

    如果使用多种机制,配置了不同方法名,则按顺序执行;如果相同,则只执行一次

为同一个bean配置的具有不同初始化方法的多个机制如下所示:

  • 1.使用@PostConstruct
    1. InitializingBean中定义afterPropertiesSet()方法
  • 3.自定义的init()方法

按相同顺序调用destroy方法:

  • 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本身就是生命周期接口的扩展。它还添加了另外两种方法来响应正在刷新和关闭的上下文。

请注意,常规的org.springframework.context.Lifecycle 接口是显式启动和停止通知的简单契约,并不意味着在上下文刷新时自动启动。对于特定bean自动启动的细粒度控制(包括启动阶段),考虑改为实现org.springframework.context.SmartLifecycle。
另外,请注意,停止通知不保证在销毁之前发出。在常规关闭时,所有 Lifecycle bean在传播常规销毁回调之前首先收到一个停止通知。但是,在上下文生存期内的热刷新或中止的刷新尝试时,只调用destroy方法。

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

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的任何“正常”生命周期对象的默认阶段是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>

“刷新”回调方法启用了SmartLifecycleBean的另一个功能。刷新上下文时(在所有对象都已实例化和初始化之后),将调用该回调方法。此时,默认的生命周期处理器检查每个SmartLifecycle对象的isautoStartup()方法的返回值。如果为true,则该对象将在该点启动,而不是等待显式调用上下文或其自身的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,启动顺序由相位值和任何“依赖”关系决定。
在非Web应用程序中优雅地关闭SpringIOC容器
本节仅适用于非Web应用程序。基于Web的ApplicationContext实现已经有了适当的代码,可以在相关Web应用程序关闭时优雅地关闭SpringIOC容器。
如果您在非Web应用程序环境(例如,在富客户机桌面环境中)中使用Spring的IOC容器,请在JVM中注册一个关闭挂钩。这样做可以确保正常关闭,并在单例bean上调用相关的销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。

要注册关闭挂钩,请调用在ConfigurableApplicationContext 接口上声明的registerShutdownBook()方法,如下示例所示:

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

        // 主方法退出,在应用程序关闭之前调用hook…
    }
}

1.6.2. 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的访问。

从Spring2.5开始,autowiring是另一个获得对ApplicationContext引用的替代方法。“传统”构造函数和byType自动装载可以分别为构造函数参数或setter方法参数提供ApplicationContext的依赖关系。为了获得更大的灵活性,包括自动装载字段和多个参数方法的能力,请使用新的基于注释的自动连接功能。如果执行此操作,则ApplicationContext将自动装载到需要ApplicationContext类型的字段、构造函数参数或方法参数中,前提是相关字段、构造函数或方法带有@Autowired注释。

当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,将向提供名称的引用。下面的列表显示了BeanNameAware接口的定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

在填充普通bean属性之后,但在初始化方法(如initializingbean、afterpropertiesset或自定义init方法)之前调用这个方法。

1.6.3. 其它Aware接口

除了ApplicationContextAware和BeanNameAware(前面讨论过)之外,Spring还提供了一系列Aware接口,给bean提供某种基础结构依赖性。作为一般规则,名称指明依赖项类型。下表总结了最重要的Aware感知接口:
表4.Aware interfaces

名称 注入依赖项
ApplicationContextAware 声明ApplicationContext。
ApplicationEventPublisherAware 封闭ApplicationContext的事件发布者。
BeanClassLoaderAware 用于加载bean类的类装入器
BeanFactoryAware 声明BeanFactory
BeanNameAware 声明bean的名称。
BootstrapContextAware 容器运行的资源适配器BootstrapContext 。通常仅在支持JCA的应用程序上下文实例中可用。
LoadTimeWeaverAware 用于在加载时处理类定义的定义的weaver。
MessageSourceAware 配置的消息解析策略(支持参数化和国际化)。
NotificationPublisherAware Spring JMX通知发布服务器。
ResourceLoaderAware 配置的加载程序,用于低级别访问资源。
ServletConfigAware 容器运行的当前ServletConfig。仅在Web应用程序上下文中有效。
ervletContextAware 容器运行的当前servletContext。仅在Web应用程序上下文中有效。

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