Spring官方文档阅读(四)之IOC容器(自定义bean的性质)

6.自定义Bean的性质

Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。

6.1生命周期回调

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

注意:

  • JSR-250 @PostConstruct和@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。 使用这些注释意味着您的bean没有耦合到特定于Spring的接口。
  • 如果您不希望使用JSR-250注解,但是您仍然希望消除耦合,请考虑init-method和destroy-method bean定义元数据。

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

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

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

6.11初始化的回调函数

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

void afterPropertiesSet() throws Exception;
  • 1

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。 另外,我们建议使用@PostConstruct注解或指定POJO初始化方法。 对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。 通过Java配置,可以使用@BeaninitMethod属性。 考虑以下示例:

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

    public void init() {
        // do some initialization work
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

前面的示例与下面的示例(由两个清单组成)具有几乎完全相同的效果:

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

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是,前面两个示例中的第一个示例没有将代码与Spring耦合。

6.12销毁函数的回调

通过实现org.springframework.beans.factory.DisposableBean接口,可以在包含bean的容器被销毁时使bean获得回调。 DisposableBean接口指定一个方法:

void destroy() throws Exception;
  • 1

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。 另外,我们建议使用@PreDestroy注解或指定bean定义支持的通用方法。 使用基于XML的配置元数据时,可以在上使用destroy-method属性。 通过Java配置,可以使用@BeandestroyMethod属性。 考虑以下定义:

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

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

前面的定义与下面的定义几乎具有完全相同的效果:

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

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

但是,前面两个定义中的第一个没有将代码与Spring耦合。

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

6.13默认的初始化和销毁方法

当编写不使用Spring特定的InitializingBeanDisposableBean回调接口的初始化和销毁方法回调时,通常会使用诸如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.");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

然后,您可以在类似于以下内容的Bean中使用该类:

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

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

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

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

Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。因此,初始化回调是在原始bean引用上调用的,这意味着AOP拦截器等还没有应用到bean。首先完全创建一个目标bean,然后应用一个带有拦截器链的AOP代理(例如)。如果目标bean和代理是分开定义的,那么您的代码甚至可以绕过代理与原始目标bean交互。因此,对其应用拦截器是不一致的。

6.14组合生命周期机制

从spring2.5开始,您有三种控制bean生命周期行为的选项:

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

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

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

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

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

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

6.15启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意,LifecycleProcessor本身就是生命周期接口的扩展。它还添加了另外两个方法,用于对刷新和关闭上下文做出反应。

请注意,常规的org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。 为了对特定bean的自动启动(包括启动阶段)进行细粒度的控制,请考虑改为实现org.springframework.context.SmartLifecycle

另外,请注意,不能保证会在销毁之前发出停止通知。 定期关闭时,在传播常规销毁回调之前,所有Lifecycle bean都会首先收到停止通知。 但是,在上下文生存期内进行热刷新或停止刷新尝试时,仅调用destroy方法。

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

public interface Phased {

    int getPhase();
}
  • 1
  • 2
  • 3
  • 4

以下清单显示了SmartLifecycle接口的定义:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

开始时,相位最低的对象先开始。当停止时,按相反的顺序执行。因此,实现SmartLifecycle并其getPhase()方法返回整数的对象。最小值应该是最先开始和最后停止的值。在频谱的另一端,一个整数相位值。最大值表示应该最后启动并首先停止该对象(可能是因为它依赖于正在运行的其他进程)。在考虑phase值时,了解任何正常生命周期对象的默认阶段也很重要

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>
  • 1
  • 2
  • 3
  • 4

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。 后者驱动关闭过程,就好像已经显式调用了stop()一样,但是它在上下文关闭时发生。 另一方面,“刷新”回调启用SmartLifecycle bean的另一个功能。 刷新上下文时(在所有对象都被实例化和初始化之后),该回调将被调用。 届时,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。 如果为true,则从那时开始启动该对象,而不是等待上下文或它自己的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。 相位值和任何“依赖”关系决定了启动顺序,如前所述。

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

本节仅适用于非web应用程序。基于web的Spring ApplicationContext实现已经准备好了代码,可以在相关的web应用程序关闭时优雅地关闭Spring IOC容器。

如果您在非Web应用程序环境中(例如,在富客户端桌面环境中)使用Spring的IOC容器,请向JVM注册一个关闭钩子。 这样做可以确保正常关机,并在您的Singleton bean上调用相关的destroy方法,以便释放所有资源。 您仍然必须正确配置和实现这些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...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

6.2ApplicationContextAware 和BeanNameAware

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

public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
  • 1
  • 2
  • 3

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

自动装配是获得对ApplicationContext的引用的另一种选择。 传统的constructorbyType自动装配模式,可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖项。 要获得更大的灵活性,包括能够自动连接字段和使用多个参数方法,请使用基于注解的自动装配功能。 如果这样做,则将ApplicationContext自动连接到需要使用ApplicationContext类型的字段,构造函数参数或方法参数中(如果有问题的字段,构造函数或方法带有@Autowired注释)。

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

public interface BeanNameAware {
	void setBeanName(String name) throws BeansException;
}
  • 1
  • 2
  • 3

在填充常规bean属性之后但在初始化回调(例如InitializingBean,afterPropertiesSet或自定义init-method)之前调用该回调。

6.3 其他Aware 接口

除了ApplicationContextAwareBeanNameAware(前面已经讨论过)之外,Spring还提供了多种Aware回调接口,这些接口使Bean向容器指示它们需要某种基础结构依赖性。 通常,名称表示依赖项类型。 下表总结了最重要的Aware接口:

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

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

7.Bean定义继承

Bean定义可以包含许多配置信息,包括构造函数参数,属性值和特定于容器的信息,例如初始化方法,静态工厂方法名称等。 子bean定义从父定义继承配置数据。 子定义可以覆盖某些值或根据需要添加其他值。 使用父bean和子bean定义可以节省很多输入。 实际上,这是一种模板形式。

如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。 大多数用户不在此级别上与他们合作。 相反,它们在诸如ClassPathXmlApplicationContext之类的类中声明性地配置Bean定义。 当使用基于XML的配置元数据时,可以通过使用父属性来指示子Bean定义,并指定父Bean作为该属性的值。 以下示例显示了如何执行此操作:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果未指定子bean定义,则使用父定义中的bean类,但也可以覆盖它。 在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。

子bean定义从父对象继承范围,构造函数参数值,属性值和方法替代,并可以选择添加新值。 您指定的任何范围,初始化方法,destroy方法或静态工厂方法设置都会覆盖相应的父设置。

其余设置始终从子定义中获取:依赖项,自动装配模式,依赖项检查,单例和惰性初始化。

前面的示例使用abstract属性将父bean定义显式标记为abstract。 如果父定义未指定类,则需要将父bean定义显式标记为抽象,如以下示例所示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

父bean不能单独实例化,因为它不完整,并且还被明确标记为抽象。 当定义是抽象的时,它只能用作纯模板bean定义,用作子定义的父定义。 通过将其称为另一个bean的ref属性或使用父bean id进行显式getBean()调用来尝试单独使用这样的抽象父bean会返回错误。 同样,容器的内部preInstantiateSingletons()方法将忽略定义为抽象的bean定义。

默认情况下,ApplicationContext会预先实例化所有单例。 因此,重要的是(至少对于单例bean),如果有一个(父)bean定义仅打算用作模板,并且此定义指定了一个类,则必须确保将abstract属性设置为true ,否则应用程序上下文将实际上(试图)预先实例化抽象bean。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值