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

1. IoC容器

文章列表

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

TODO 文章较长,目前翻译到1.8

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
ApplicationEventPublisherAware ApplicationContext内部的事件发布者 ApplicationContext附加的能力
BeanClassLoaderAware 用于加载Bean的类的类加载器 实例化Bean
BeanFactoryAware 声明的BeanFactory. BeanFactory API
BeanNameAware 声明的Bean的名称 ApplicationContextAwareBeanNameAware
LoadTimeWeaverAware 为在加载时处理类定义而定义的编织器。 在Spring框架中使用AspectJ进行加载时编织
MessageSourceAware 解析消息的已配置策略(支持参数化和国际化)。 ApplicationContext附加的能力
NotificationPublisherAware Spring 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实例本身。

猜你喜欢

转载自juejin.im/post/7240380161555021884