1.6 自定义bean的性质

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框架使用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配置,您可以使用@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在包含它的容器被销毁时获得回调。该接口指定了一个方法:

void destroy() throws Exception;

建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定bean定义支持的泛型方法。对于基于xml的配置元数据,您可以在< bean/>上使用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 {

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

但不耦合代码到Spring。

< bean>元素的destroy-method属性可以分配一个特殊的(推断的)值,该值指示Spring自动检测特定bean类(任何实现java.lang.AutoClosable或java.io.Closable的类)上的公共close或shutdown方法。还可以在< beans>元素的default-destroy-method属性上设置这个特殊的(推断出来的)值,以便将此行为应用于整个bean集(请参阅缺省初始化和销毁方法)。注意,这是Java配置的默认行为。

默认初始化和销毁方法

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

您可以配置Spring容器来查找每个bean上的命名初始化和销毁回调方法名。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用一个名为init()的初始化回调函数,而不必为每个bean定义配置init-method="init"属性。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.");
                }
        }
}
<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属性,可以类似地配置destroy方法回调(即在XML中)。

现有的bean类已经有回调方法,它们的命名与约定不一致,您可以使用< 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配置多个生命周期机制,调用如下:

  • 使用@PostConstruct注释的方法
  • afterPropertiesSet(),由InitializingBean回调接口定义
  • 自定义配置的init()方法

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

  • 使用@PreDestroy注释的方法
  • destroy(),由DisposableBean回调接口定义
  • 自定义配置的destroy()方法

启动和关闭回调

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

public interface Lifecycle {

        void start();

        void stop();

        boolean isRunning();
}

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

public interface LifecycleProcessor extends Lifecycle {

        void onRefresh();

        void onClose();
}

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

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

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

public interface Phased {

        int getPhase();
}
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来覆盖默认的lifecycle processor实例。如果您只想修改超时,那么定义以下内容就足够了:

<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()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。“相位”值以及任何“依赖”关系将以与上面描述的相同的方式确定启动顺序。

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

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

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

要注册一个关闭钩子,您可以调用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...
        }
}

1.6.2 ApplicationContextAware 和 BeanNameAware

当ApplicationContext创建实现org.springframework.context.ApplicationContextAware的对象实例时。实例提供了对该应用程序上下文的引用。

public interface ApplicationContextAware {

        void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

从Spring 2.5开始,自动装配是获取对ApplicationContext引用的另一种选择。“传统”构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖关系。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用新的基于注释的自动装配特性。如果您这样做了,那么ApplicationContext将被自动拖放到一个字段、构造函数参数或方法参数中,如果该字段、构造函数或方法携带@Autowired注释,则该字段、构造函数或方法将期望得到ApplicationContext类型。更多信息,请参见@Autowired。

当应用程序上下文创建一个实现org.springframework.beans.factory.BeanNameAware的类时。类提供了对其关联对象定义中定义的名称的引用。

public interface BeanNameAware {

        void setBeanName(String name) throws BeansException;
}

回调函数在填充普通bean属性之后调用,但在初始化回调(如InitializingBean afterPropertiesSet或自定义init-method)之前调用。

其他Aware接口

除了上面讨论的ApplicationContextAware和BeanNameAware, Spring还提供了一系列Aware接口,允许bean向容器表明它们需要某种基础设施依赖关系。下面总结了最重要的Aware接口——作为一个通用规则,名称很好地指示了依赖类型:

NameInjected DependencyExplained in…​
ApplicationContextAwareDeclaring ApplicationContextApplicationContextAware and BeanNameAware
ApplicationEventPublisherAwareEvent publisher of the enclosing ApplicationContextAdditional Capabilities of the ApplicationContext
BeanClassLoaderAwareClass loader used to load the bean classesInstantiating beans
BeanFactoryAwareDeclaring BeanFactoryApplicationContextAware and BeanNameAware
BeanNameAwareName of the declaring beanApplicationContextAware and BeanNameAware
BootstrapContextAwareResource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContextsApplicationContextAware and BeanNameAware
LoadTimeWeaverAwareDefined weaver for processing class definition at load timeApplicationContextAware and BeanNameAware
MessageSourceAwareConfigured strategy for resolving messages (with support for parametrization and internationalization)ApplicationContextAware and BeanNameAware
NotificationPublisherAwareSpring JMX notification publisherApplicationContextAware and BeanNameAware
ResourceLoaderAwareConfigured loader for low-level access to resourcesApplicationContextAware and BeanNameAware
ServletConfigAwareCurrent ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContextApplicationContextAware and BeanNameAware
ServletContextAwareCurrent ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContextApplicationContextAware and BeanNameAware

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值