Spring Framework 6.0.3 Customizing the Nature of a Bean(定制bean的性质)和bean定义继承 翻译

169 阅读16分钟

1.6 Customizing the Nature of a Bean(定制bean的性质)

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

  • 生命周期回调
  • ApplicationContextAware和BeanNameAware
  • 其他感知接口

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 bean定义元数据。

在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到并调用适当方法的任何回调接口。如果您需要自定义特性或Spring默认情况下没有提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点

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

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

Initialization回调

initializingbean接口允许bean在容器在bean上设置了所有必要的属性之后执行初始化工作。InitializingBean接口指定了一个方法:

void afterPropertiesSet() throws Exception;

我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于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耦合。

破坏回调

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

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 {

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


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

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

默认初始化和销毁方法

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

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

假设初始化回调方法名为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属性,可以类似地(即在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注释的方法
  • 由InitializingBean回调接口定义的afterPropertiesSet()
  • 自定义配置的init()方法

Destroy方法的调用顺序相同:

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

启动和关闭回调

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

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

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

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

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

注意,常规的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 {
    int DEFAULT_PHASE = Integer.MAX_VALUE;

    default boolean isAutoStartup() {
        return true;
    }

    default void stop(Runnable callback) {
        this.stop();
        callback.run();
    }

    default int getPhase() {
        return Integer.MAX_VALUE;
    }
}

启动时,相位最低的物体先启动。当停止时,遵循相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回Integer的对象。MIN_VALUE将是最先启动和最后停止的。在频谱的另一端,整数的相位值。MAX_VALUE将指示对象应该最后启动并首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道任何“正常”生命周期对象没有实现SmartLifecycle的默认阶段是0。因此,任何负相位值都表明对象应该在这些标准组件之前开始(并在它们之后停止)。相反,任何正相位值都是正确的。

SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这允许在必要时异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor等待每个阶段内的对象组的超时值来调用该回调。默认的每阶段超时时间为30秒。您可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的生命周期处理器实例。如果你只想修改超时时间,定义如下就足够了:

public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {
    private final Log logger = LogFactory.getLog(this.getClass());
    private volatile long timeoutPerShutdownPhase = 30000L;
    //..........
    }
 <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()方法返回的布尔值。如果为真,则该对象在该点启动,而不是等待显式调用上下文的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。如前所述,阶段值和任何“依赖”关系决定启动顺序。

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

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

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

要注册一个关机钩子,调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下例所示:

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接口的对象实例时,该实例将被提供一个对该ApplicationContext的引用。下面的清单显示了ApplicationContextAware接口的定义:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

Autowiring是获取ApplicationContext引用的另一种选择。传统的构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供类型为ApplicationContext的依赖项。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,请使用基于注释的自动装配特性。如果这样做,则ApplicationContext将自动连接到一个字段、构造函数参数或方法参数中,如果所讨论的字段、构造函数或方法携带@Autowired注释,则这些字段、构造函数参数或方法参数将期望ApplicationContext类型。有关更多信息,请参见使用@Autowired

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}


该回调在填充普通bean属性之后调用,但在初始化回调(如InitializingBean.afterPropertiesSet()或自定义初始化方法之前调用。

1.6.3. 其他Aware接口

除了ApplicationContextAware和BeanNameAware(前面讨论过),Spring还提供了广泛的Aware回调接口,让bean向容器指示它们需要某种基础设施依赖关系。作为一般规则,名称表示依赖项类型。下表总结了最重要的Aware接口:

NameInjected DependencyExplained in…​
ApplicationContextAwareDeclaring ApplicationContext.ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAwareEvent publisher of the enclosing ApplicationContext.Additional Capabilities of the ApplicationContext
BeanClassLoaderAwareClass loader used to load the bean classes.Instantiating Beans
BeanFactoryAwareDeclaring BeanFactory.The BeanFactory API
BeanNameAwareName of the declaring bean.ApplicationContextAware and BeanNameAware
LoadTimeWeaverAwareDefined weaver for processing class definition at load time.Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAwareConfigured strategy for resolving messages (with support for parametrization and internationalization).Additional Capabilities of the ApplicationContext
NotificationPublisherAwareSpring JMX notification publisher.Notifications
ResourceLoaderAwareConfigured loader for low-level access to resources.Resources
ServletConfigAwareCurrent ServletConfig the container runs in. Valid only in a web-aware SpringApplicationContext.Spring MVC
ServletContextAwareCurrent ServletContext the container runs in. Valid only in a web-aware SpringApplicationContext.Spring MVC

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

1.7. Bean定义继承

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>

子bean定义使用来自父bean定义的bean类(如果没有指定的话),但也可以重写它。在后一种情况下,子bean类必须与父bean类兼容(也就是说,它必须接受父bean的属性值)。

子bean定义继承父bean的作用域、构造函数参数值、属性值和方法重写,并可以选择添加新值。您指定的任何范围、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父设置。

其余设置始终取自子定义:依赖、自动连接模式、依赖项检查、单例和惰性初始化。

前面的例子通过使用抽象属性显式地将父bean定义标记为抽象。如果父bean定义没有指定类,则需要显式地将父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>

父bean不能单独实例化,因为它是不完整的,而且它还被显式地标记为抽象。当一个定义是抽象的,它只能作为纯模板bean定义使用,用作子定义的父定义。试图单独使用这样一个抽象的父bean,比如将其引用为另一个bean的ref属性,或者使用父bean ID显式调用getBean(),将返回一个错误。类似地,容器的内部preInstantiateSingletons()方法会忽略被定义为抽象的bean定义。

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