2.6 Spring Framework 5.x 之 自定义Bean的本质(Bean的生命周期回调)

1.6 定制Bean的本质

Spring Framework提供了许多可用于自定义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-method和destroy-method对象定义元数据。

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

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

destory回调

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

void destroy() throws Exception;

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

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

您可以destroy-method为元素的属性分配一个特殊 (inferred)值,该值指示Spring自动检测特定bean类的公共close或 shutdown方法。(任何实现 java.lang.AutoCloseable或java.io.Closeable将匹配的类。)您还可以(inferred)在元素的default-destroy-method属性 上设置此特殊值,以将此行为应用于整组bean(请参阅 默认初始化和销毁…方法)。请注意,这是Java配置的默认行为。

默认初始化和销毁方法

当你写的初始化和销毁不使用Spring的具体方法回调InitializingBean和DisposableBean回调接口,你通常写有名字,如方法init(),initialize(),dispose(),等等。理想情况下,此类生命周期回调方法的名称在项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

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

假设您的初始化回调方法已命名,init()并且您的destroy回调方法已命名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>

default-init-method顶级元素属性上存在属性会导致Spring IoC容器init将bean类上调用的方法识别为初始化方法回调。当bean被创建和组装时,如果bean类具有这样的方法,则在适当的时候调用它。

您可以通过使用default-destroy-method顶级元素上的属性来类似地配置destroy方法回调(在XML中) 。

如果现有的bean类已经具有以约定方式命名的回调方法,则可以通过使用 自身的init-method和destroy-method属性指定(在XML中,即方法名称)来覆盖默认值。

Spring容器保证在为bean提供所有依赖项之后立即调用已配置的初始化回调。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等尚未应用于bean。首先完全创建目标bean,然后应用带有拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,那么您的代码甚至可以绕过代理与原始目标bean进行交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到其代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。

结合生命周期机制

从Spring 2.5开始,您有三个控制bean生命周期行为的选项:

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

如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则每个配置的方法都按照此注释后列出的顺序执行。但是,如果init()为多个生命周期机制配置了相同的方法名称(例如, 对于初始化方法),则该方法将执行一次,如上 一节中所述。

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

  1. 用注解方法 @PostConstruct
  2. afterPropertiesSet()由InitializingBean回调接口定义
  3. 自定义配置的init()方法

Destroy方法以相同的顺序调用:

  1. 用注释方法注释 @PreDestroy
  2. destroy()由DisposableBean回调接口定义
  3. 自定义配置的destroy()方法

startup启动和shutdown关闭回调
该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。

此外,请注意,在销毁之前不保证停止通知。在常规关闭时,所有Lifecyclebean在传播一般销毁回调之前首先收到停止通知。但是,在上下文生命周期中的热刷新或中止刷新尝试时,仅调用destroy方法。

启动和关闭调用的顺序非常重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并且在其依赖之前停止。但是,有时,直接依赖性是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即getPhase()在其超级接口上定义的方法 Phased。以下清单显示了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将指示对象应该最后启动并首先停止(可能因为它依赖于正在运行的其他进程)。当考虑相位值,同样重要的是要知道,对于任何“正常”的默认阶段 Lifecycle目标没有实现SmartLifecycle的0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(并在它们之后停止)。任何正相值都是相反的。

定义的stop方法SmartLifecycle接受回调。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’回调启用了SmartLifecyclebean的另一个功能 。刷新上下文时(在实例化并初始化所有对象之后),将调用该回调。此时,默认生命周期处理器检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值 。如果true,那个对象是在那个点开始的,而不是等待显式调用上下文或它自己的对象start()方法(与上下文刷新不同,上下文启动不会自动发生在标准上下文实现中)。该phase值与任何“依赖式”的关系确定为前面所述的启动顺序。

1.6.2 ApplicationContextAware和BeanNameAware

当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,将 为该实例提供对该实例的引用ApplicationContext。以下清单显示了ApplicationContextAware界面的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean可以ApplicationContext通过ApplicationContext接口以编程方式操作创建它们的方法,或者通过将引用转换为此接口的已知子类(例如ConfigurableApplicationContext,公开其他功能)。一种用途是对其他bean进行编程检索。有时这种能力很有用。但是,一般情况下,您应该避免使用它,因为它将代码耦合到Spring并且不遵循Inversion of Control样式,其中协作者作为属性提供给bean。其他方法 ApplicationContext提供对文件资源的访问,发布应用程序事件和访问MessageSource。这些附加功能在附加ApplicationContext功能中描述 。

从Spring 2.5开始,autowiring是另一种获取引用的方法 ApplicationContext。“传统” constructor和byType自动装配模式(如自动装配协作者中所述)可以分别为ApplicationContext构造函数参数或setter方法参数提供类型的依赖性 。要获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注释的新自动装配功能。如果这样做,ApplicationContext则自动装入一个字段,构造函数参数或方法参数,ApplicationContext如果相关的字段,构造函数或方法带有@Autowired注释,则该参数需要该类型。有关更多信息,请参阅 使用@Autowired。

当ApplicationContext创建实现org.springframework.beans.factory.BeanNameAware接口的类时,将为 该类提供对其关联对象定义中定义的名称的引用。以下清单显示了BeanNameAware接口的定义:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回调正常bean属性的人口之后,但在一个初始化回调诸如调用InitializingBean,afterPropertiesSet或自定义的初始化方法。

1.6.3. 其他Aware接口

除了ApplicationContextAware和BeanNameAware讨论( 早期),Spring提供了一系列Aware可以让豆子指示,他们需要一定的基础设施的依赖容器接口。作为一般规则,名称是依赖类型的良好指示。下表总结了最重要的Aware接口:
表4.感知接口

名称 注入依赖 解释在…
ApplicationContextAware 声明ApplicationContext ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware 封闭的事件发布者ApplicationContext Additional Capabilities of the ApplicationContext
BeanClassLoaderAware 用于加载bean类的类加载器 实例化的Bean
BeanFactoryAware 声明一个BeanFactory ApplicationContextAware and BeanNameAware
BeanNameAware 声明Bean的名字 ApplicationContextAware 和BeanNameAware
BootstrapContextAware BootstrapContext容器运行的资源适配器。通常仅在JCA感知ApplicationContext实例中可用。 JCA CCI
LoadTimeWeaverAware 定义的weaver用于在加载时处理类定义。 Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware 用于解析消息的已配置策略(支持参数化和国际化) Additional Capabilities of the ApplicationContext
NotificationPublisherAware Spring JMX通知发布者。 Notifications
ResourceLoaderAware 配置的加载程序,用于对资源进行低级访问。 Resources
ServletConfigAware 当前ServletConfig容器运行。仅在 web-aware Spring ApplicationContext中有效 Spring MVC
ServletContextAware 当前ServletContext容器运行。仅在 web-aware Spring ApplicationContext中有效 Spring MVC

请再次注意,使用这些接口会将您的代码绑定到Spring API,而不会遵循Inversion of Control样式。因此,我们建议将它们用于需要以编程方式访问容器的基础架构bean。

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

本节仅适用于非Web应用程序。Spring的基于Web的 ApplicationContext实现已经具有代码,可以在相关Web应用程序关闭时正常关闭Spring IoC容器。

如果在非Web应用程序环境中使用Spring的IoC容器(例如,在富客户机桌面环境中),请使用JVM注册关闭挂钩。这样做可确保正常关闭并在单例bean上调用相关的destroy方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。

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

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

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86302177