Spring5中文文档【5】- IOC容器之自定义Bean的性质及 Bean继承

1. 前言

本系列基于最新5.3.10版本,大部分内容copy于官方文档…
官方文档地址

Spring Framework 提供了许多可用于自定义 bean 性质的接口。本节将它们分组如下:

  • 生命周期回调

  • ApplicationContextAware 和 BeanNameAware接口

  • 其他Aware接口

2. 生命周期回调

要让容器对 bean 生命周期的管理进行交互,可以实现 SpringInitializingBean和DisposableBean接口。然后在实现afterPropertiesSet()和destroy()方法,让 bean 在初始化和销毁​​ bean 时执行某些操作。

JSR-250标准注解@PostConstruct@PreDestroy注解通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 不会耦合到 Spring 特定的接口。

如果您不想使用 JSR-250 注解但仍想移除耦合,请考虑init-method和destroy-methodbean 标签定义元数据。

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

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

2.1 初始化回调

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 {
    
    

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

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

2.2 销毁回调

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

void destroy() throws Exception;

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

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

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

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

2.3 默认初始化和销毁​​方法

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

您可以将 Spring 容器配置为“查找”命名初始化并销毁每个 bean 上的回调方法名称。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为 的初始化回调 init(),而无需为init-method="init"每个 bean 定义配置属性。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>

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

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

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

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

2.4 结合生命周期机制

从 Spring 2.5 开始,您可以通过三个选项来控制 bean 生命周期行为:

  • InitializingBean和 DisposableBean回调接口

  • 自定义init()和destroy()方法

  • @PostConstruct@PreDestroy 注解

您可以组合这些机制来控制给定的 bean。如果为一个 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个配置的方法将按照本注释后面列出的顺序运行。但是,如果init()为多个生命周期机制配置了相同的方法名称(例如, 为初始化方法),则该方法将运行一次,如 前一节所述。

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

  • 带有注释的方法 @PostConstruct

  • afterPropertiesSet()由InitializingBean回调接口定义

  • 自定义配置init()方法

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

  • 带有注释的方法 @PreDestroy

  • destroy()由DisposableBean回调接口定义

  • 自定义配置destroy()方法

2.5 启动和关闭回调

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

public interface Lifecycle {
    
    

    void start();

    void stop();

    boolean isRunning();
}

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

public interface LifecycleProcessor extends Lifecycle {
    
    

    void onRefresh();

    void onClose();
}

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

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

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

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并在其依赖之前停止。然而,有时,直接依赖是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的.getPhase()方法 。以下清单显示了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()已被显式调用一样,但它会在上下文关闭时发生。另一方面,“刷新”回调启用了SmartLifecyclebean 的另一个功能 。当上下文刷新时(在所有对象都被实例化和初始化之后),该回调被调用。此时,默认生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值 。如果true,则该对象在该点启动,而不是等待上下文或其自身的显式调用start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。phase如前所述,值和任何“依赖”关系确定启动顺序。

2.6 在非 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...
    }
}

3. ApplicationContextAware和BeanNameAware

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

public interface ApplicationContextAware {
    
    

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

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

自动装配是获取对 ApplicationContext. 的传统的 constructor和byType自动装配模式(在所描述的自动装配协作者)可以提供类型的依赖 ApplicationContext于构造器参数或设置器方法参数,分别。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注释的自动装配功能。如果您这样做,则ApplicationContext将自动装配到字段、构造函数参数或方法参数中,ApplicationContext如果所讨论的字段、构造函数或方法带有@Autowired注释,则该类型需要该类型。有关更多信息,请参阅 使用@Autowired.

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

public interface BeanNameAware {
    
    

    void setBeanName(String name) throws BeansException;
}

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

4. 其他Aware接口

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

姓名 注入依赖 在…中解释
ApplicationContextAware 声明ApplicationContext. ApplicationContextAware 和 BeanNameAware
ApplicationEventPublisherAware 封闭的事件发布者ApplicationContext。 附加功能 ApplicationContext
BeanClassLoaderAware 类加载器用于加载 bean 类。 实例化 Bean
BeanFactoryAware 声明BeanFactory. BeanFactory
BeanNameAware 声明 bean 的名称。 ApplicationContextAware 和 BeanNameAware
LoadTimeWeaverAware 用于在加载时处理类定义的定义编织器。 在 Spring 框架中使用 AspectJ 进行加载时编织
MessageSourceAware 用于解析消息的配置策略(支持参数化和国际化)。 附加功能 ApplicationContext
NotificationPublisherAware Spring JMX 通知发布者。 通知
ResourceLoaderAware 为低级资源访问配置加载器。 资源
ServletConfigAware 当前ServletConfig容器运行。仅在 web-aware Spring 中有效 ApplicationContext。 Spring MVC
ServletContextAware 当前ServletContext容器运行。仅在 web-aware Spring 中有效 ApplicationContext。 Spring MVC

再次注意,使用这些接口会将您的代码与 Spring API 联系起来,并且不遵循控制反转风格。因此,我们建议将它们用于需要以编 程方式访问容器的基础设施 bean。

4. Bean 定义继承

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

如果您以ApplicationContext编程方式使用接口,则子 bean 定义由ChildBeanDefinition类表示。大多数用户不会在这个级别上与他们合作。相反,它们在类中以声明方式配置 bean 定义,例如ClassPathXmlApplicationContext. 当您使用基于 XML 的配置元数据时,您可以通过使用parent属性来指示子 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>

注意parent属性。

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

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

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

前面的示例使用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>

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

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

猜你喜欢

转载自blog.csdn.net/qq_43437874/article/details/120667771
今日推荐