likes
comments
collection
share

Spring框架5.3.27官方文档(中文翻译)—— 核心技术-IoC容器(二)

作者站长头像
站长
· 阅读数 21

1. IoC容器

文章列表

原文:docs.spring.io/spring-fram…

1.6. 自定义Bean的性质(Customizing the Nature of a Bean)

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

1.6.1. 生命周期回调(Lifecycle Callbacks)

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

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

补充

实现bean的生命周期管理有三种方式:

  • 实现InitializingBeanDisposableBean,存在和Spring接口耦合,不推荐使用
  • 使用JSR-250的@PostConstruct@PreDestroy,将bean和特定接口解耦
  • 使用XML的init-methoddestroy-method

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

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

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

初始化回调(Initialization Callbacks)

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

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。在Java配置中,您可以使用@Bean的initMethod属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑下面的例子:

<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耦合

销毁回调(Destruction Callbacks)

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

void destroy() throws Exception;

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PreDestroy注解或指定bean定义支持的泛型方法。对于基于XML的配置元数据,您可以在<bean/>上使用destroy-method属性。在Java配置中,您可以使用@Bean的destroyMethod属性。参见接收生命周期回调(Receiving Lifecycle Callbacks)。考虑以下定义:

<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耦合

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

默认的初始化和销毁方法(Default Initialization and Destroy Methods)

当您编写不使用Spring特定的InitializingBeanDisposableBean回调接口的初始化和销毁方法回调时,您通常使用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-methoddestroy-method属性指定(在XML中)方法名来覆盖默认值。

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

组合生命周期机制(Combining Lifecycle Mechanisms)

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

您可以组合这些机制,用于控制给定的Bean。

提示

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

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

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

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

  1. @PreDestroy注解的方法
  2. DisposableBean回调接口定义的destroy()
  3. 一个自定义配置的destroy()方法

启动和关闭的回调(Startup and Shutdown Callbacks)

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

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

启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,并在其依赖之前停止。然而,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一类型的对象开始。在这些情况下,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将是第一个启动最后一个停止的。相反,阶段值为IntegerMAX_VALUE表示对象应该最后启动,首先停止(可能是因为它依赖于正在运行的其他进程)。在考虑阶段值时,同样重要的是要知道,任何没有实现SmartLifecycle的“正常”生命周期对象的默认阶段都是0。因此,任何负阶段值都表示一个对象应该在这些标准组件之前启动(并在它们之后停止)。相反,对于任何正阶段值都是正确的。

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()一样,但它发生在上下文关闭时。另一方面,“刷新”回调启用了SmartLifecycle bean的另一个特性。当上下文被刷新时(在所有对象被实例化和初始化之后),将调用该回调。此时,默认的生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为true,则在这时启动该对象,而不是等待上下文或其自身的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。阶段(phase)值和任何“依赖”关系决定了前面描述的启动顺序。

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

提示

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

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

要注册一个关闭钩子,调用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");

        // 为上下文添加一个钩子
        ctx.registerShutdownHook();

        // 运行程序

        // main方法关闭,钩子在程序关闭前被调用
    }
}

1.6.2. AplicationContextAwareBeanNameAware

当一个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的附加功能)Additional Capabilities of the ApplicationContext.中有描述。

自动装配是获取对ApplicationContext引用的另一种替代方法。传统的构造函数和byType自动装配模式(如自动装配协作器(Autowiring Collaborators)中所述)可以分别为构造函数参数setter方法参数提供ApplicationContext类型的依赖。要获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注解的自动装配特性。如果你这样做了,ApplicationContext被自动装配到一个字段、构造函数参数或方法参数中,如果有问题的字段、构造函数或方法带有@Autowired注解,则该字段、构造函数参数或方法参数需要ApplicationContext类型。有关更多信息,请参见使用@Autowired(Using @Autowired

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

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

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

1.6.3. 其它Aware接口

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

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

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

1.7. bean定义的继承

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

如果您以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户不会在这个级别上使用它们。相反,它们在类(如ClassPathXmlApplicationContext)中声明式地配置bean定义。当您使用基于xml的配置元数据时,您可以通过使用parent属性来指示子bean定义,并将父bean指定为该属性的值。下面的例子展示了如何这样做:

<!-- 在指定了class的情况下,可以不指定abstract -->
<bean id="inheritedBean" abstract="true" class="TestBean">
	<property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!-- parent指定需要继承配置的bean的名称 -->
<bean id="inhertedChild" class="ChildBean" parent="inheritedBean" init-method="init"> <!-- 1 -->
	<!-- 重写属性配置 -->
    <property name="name" value="override"/>
    <!-- age继承父配置 -->
</bean>
  • 1处:注意parent属性

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

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

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

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

<!-- 在未指定class的情况下,必须指定abstract -->
<bean id="inheritedBean" abstract="true" >
	<property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!-- parent指定需要继承配置的bean的名称 -->
<bean id="inhertedChild" class="ChildBean" parent="inheritedBean" init-method="init">
	<!-- 重写属性配置 -->
    <property name="name" value="override"/>
    <!-- age继承父配置 -->
</bean>

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

提示

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

1.8. 容器扩展点(Container Extension Points)

通常,应用程序开发人员不需要创建ApplicationContext实现类的子类。相反,可以通过插入特殊集成接口的实现来扩展Spring IoC容器。接下来的几节将描述这些集成接口。

1.8.1. 使用BeanPostProcessor定制Bean

BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器的默认值)实例化逻辑、依赖项解析逻辑等等。如果您想在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,您可以插入一个或多个自定义BeanPostProcessor实现。

您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例运行的顺序。只有当BeanPostProcessor实现了Ordered接口时,你才能设置这个属性。如果编写自己的BeanPostProcessor,也应该考虑实现Ordered接口。要了解更多细节,请参阅BeanPostProcessorOrdered接口的javadoc。参见程序化注册BeanPostProcessor实例(programmatic registration of BeanPostProcessor instances)说明。

提示

BeanPostProcessor实例对bean(或对象)实例进行操作。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例完成它们的工作。

BeanPostProcessor实例的作用域为每个容器。这只有在使用容器层次结构时才有意义。如果在一个容器中定义了BeanPostProcessor,则它只对该容器中的bean进行后处理。换句话说,在一个容器中定义的bean不会被在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是同一层次结构的一部分。

更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定义配置元数据(Customizing Configuration Metadata with a BeanFactoryPostProcessor中所述。

BeanPostProcessor接口由两个回调方法组成。当这样的类被注册为容器的后处理器(post-processor)时,对于容器创建的每个bean实例,后处理器

  • 容器初始化方法(如InitializingBean.afterPropertiesSet()或任何声明的init方法)被调用之前
  • 以及在任何bean初始化回调之后都会从容器获得回调。

后处理器可以对bean实例采取任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者它可能用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。

ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理器,以便稍后在创建bean时调用它们。Bean后处理器可以以与任何其他Bean相同的方式部署在容器中

注意,当通过在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器性质。否则,ApplicationContext不能在完全创建它之前按类型自动检测它。由于需要尽早实例化BeanPostProcessor以便应用于上下文中其他bean的初始化,因此这种早期类型检测至关重要

提示

以编程方式注册BeanPostProcessor实例

虽然BeanPostProcessor注册的推荐方法是通过ApplicationContext自动检测(如前所述),但您可以通过使用addBeanPostProcessor方法以编程方式在ConfigurableBeanFactory上注册它们。当您需要在注册之前评估条件逻辑,或者甚至在层次结构中跨上下文复制bean后处理器时,这是非常有用的。但是请注意,以编程方式添加的BeanPostProcessor实例不遵循Ordered接口。在这里,是注册的顺序决定了执行的顺序。还要注意,无论显式排序如何,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前被处理。

BeanPostProcessor实例和AOP自动代理

实现BeanPostProcessor接口的类是特殊的,并且被容器以不同的方式对待。所有**BeanPostProcessor实例和它们直接引用的bean在启动时被实例化**,作为ApplicationContext特殊启动阶段的一部分。接下来,以排序的方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以BeanPostProcessor实例和它们直接引用的bean都不适合自动代理,因此,没有将切面编织到它们中。

对于任何这样的bean,您应该看到一条信息日志消息:bean someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)。

如果通过使用自动装配@Resource(可能会回到自动装配)将bean装配到BeanPostProcessor中,那么在搜索类型匹配依赖候选项时,Spring可能会访问意外的bean,从而使它们不适合自动代理其他类型的bean后处理。例如,如果您有一个带有@Resource注解的依赖项,其中字段或setter名称不直接对应于bean声明的名称,并且没有使用name属性,Spring将访问其他bean以按类型匹配它们。

下面的例子展示了如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。

示例:Hello World, BeanPostProcessor风格

第一个示例说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,该实现在容器创建每个bean时调用toString()方法,并将结果字符串打印到系统控制台。

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面的bean元素使用了InstantiationTracingBeanPostProcessor:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

注意InstantiationTracingBeanPostProcessor是如何定义的。它甚至没有名称,而且因为它是一个bean,所以可以像其他bean一样对它进行依赖注入。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring的动态语言支持详见动态语言支持(Dynamic Language Support)一章。

下面的Java应用程序运行上述代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

上述应用程序的输出类似于以下内容:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

示例:AutowiredAnnotationBeanPostProcessor

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IoC容器的常用方法。一个例子是Spring的AutowiredAnnotationBeanPostProcessor——一个随Spring发行版附带的BeanPostProcessor实现,它可以自动装配带注解的字段setter方法任意配置方法

1.8.2. 使用BeanFactoryPostProcessor自定义配置元数据

我们要看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义与BeanPostProcessor类似,但有一个主要区别:BeanFactoryPostProcessorbean配置元数据上操作。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。

您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例运行的顺序。但是,只有当BeanFactoryPostProcessor实现了Ordered接口时,您才能设置此属性。如果编写自己的BeanFactoryPostProcessor,也应该考虑实现Ordered接口。有关更多细节,请参阅BeanFactoryPostProcessorOrdered接口的javadoc。

提示

如果希望更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(前面在使用BeanPostProcessor定制bean中描述过)。虽然在技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,通过使用BeanFactory.getBean()),但这样做会导致过早的bean实例化,违反标准的容器生命周期。这可能会导致负面的副作用,比如绕过bean的后期处理。

此外,BeanFactoryPostProcessor实例的作用域为每个容器。这只有在使用容器层次结构时才有意义。如果在一个容器中定义了BeanFactoryPostProcessor,则它只应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器是同一层次结构的一部分。

当在ApplicationContext中声明bean工厂后处理器时,它会自动运行,以便将更改应用于定义容器的配置元数据。Spring包括许多预定义的bean工厂后处理器,如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。

ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候将这些bean用作bean工厂后处理器。您可以像部署任何其他bean一样部署这些后处理器bean

提示

BeanPostProcessor一样,您通常不希望将BeanFactoryPostProcessors配置为延迟初始化。如果没有其他bean引用BeanFactoryPostProcessor,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,并且即使您在声明<beans/>元素时将default-lazy-init属性设置为true, BeanFactoryPostProcessor也将被立即实例化。

示例:类名替换PropertySourcesPlaceholderConfigurer

您可以使用PropertySourcesPlaceholderConfigurer通过使用标准Java Properties格式将bean定义中的属性值外部化到单独的文件中。这样做使部署应用程序的人员能够自定义特定于环境的属性,例如数据库url和密码,而无需修改容器的主XML定义文件的复杂性或风险。

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

该示例显示了从外部Properties文件配置的属性。在运行时,将PropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。要替换的值被指定为${property-name}形式的占位符,它遵循Ant和log4j以及JSP EL样式。

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

因此,${jdbc.username} 字符串在运行时被替换为值'sa',对于与属性文件中的键匹配的其他占位符值也是如此。PropertySourcesPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,还可以自定义占位符前缀和后缀。

使用Spring 2.5中引入的上下文命名空间,您可以使用专用的配置元素配置属性占位符。您可以在location属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer不仅在您指定的Properties文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,它将检查Spring Environment属性和常规Java系统属性。

您可以使用PropertySourcesPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子展示了如何这样做:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/something/strategy.properties</value>
    </property>
    <!-- 配置属性 -->
    <property name="properties">
        <value>custom.strategy.class=com.something.DefaultStrategy</value>
    </property>
</bean>

<!-- 引用属性,替换类名 -->
<bean id="serviceStrategy" class="${custom.strategy.class}"/>

补充

  • properties中配置了key为custom.strategy.class,值为com.something.DefaultStrategy
  • Bean serviceStrategyclass属性取了custom.strategy.class对应的值

如果不能在运行时将类解析为有效类,则在即将创建bean时解析失败,这是在非惰性初始化bean的ApplicationContextpreinstantiatesingleton()阶段。

示例:PropertyOverrideConfigurer

另一个bean工厂后处理程序PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer类似,但与后者不同的是,原始定义可以为bean属性提供默认值,也可以根本没有值。如果重写的Properties文件没有某个bean属性的条目,则使用默认上下文定义。

注意,bean定义不知道被覆盖了,因此从XML定义文件中不能立即看出正在使用覆盖配置器。如果多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,由于覆盖机制,最后一个实例获胜

属性文件配置行采用以下格式:

beanName.property=value

下面的清单显示了该格式的一个示例:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

该示例文件可以与容器定义一起使用,该容器定义包含一个名为dataSource的bean,该bean具有driverurl属性。

也支持复合属性名,只要路径的每个组件(除了被覆盖的最后一个属性)都是非空的(可能是由构造函数初始化的)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123

tom.fred.bob.sammy=123

提示

指定的覆盖值总是文字值。它们不会被翻译成bean引用。当XML bean定义中的原始值指定一个bean引用时,这种约定也适用。

随着Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素来配置属性重写,如下面的示例所示:

<context:property-override location="classpath:override.properties"/>

1.8.3. 使用FactoryBean定制实例化逻辑(Customizing Instantiation Logic with a FactoryBean

您可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。

FactoryBean接口是可插入Spring IoC容器实例化逻辑的一个点。如果您有复杂的初始化代码用Java更好地表示,而不是(可能)冗长的XML,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。

FactoryBean<T>接口提供三个方法:

  • T getObject():返回此工厂创建的对象的实例。实例可能是共享的,这取决于该工厂返回的是单例还是原型。
  • boolean isSingleton():如果此FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回true
  • Class<?> getObjectType():返回getObject()方法返回的对象类型,如果事先不知道该类型,则返回null。

FactoryBean概念和接口在Spring框架中的许多地方都有使用。超过50个FactoryBean接口的实现随Spring本身一起发布。

当您需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,请在调用ApplicationContextgetBean()方法时,在bean的id前面加上&符号。

因此,对于id为myBean的给定FactoryBean

  • 在容器上调用getBean(“myBean”)将返回FactoryBean的产品,
  • 而调用getBean(“&myBean”)将返回FactoryBean实例本身。
转载自:https://juejin.cn/post/7240380161555021884
评论
请登录