Spring5中文文档【6】IOC容器之容器扩展点

前言

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

1. 容器扩展点

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

1.1 通过使用自定义 Bean(BeanPostProcessor)

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

您可以配置多个BeanPostProcessor实例,并且可以BeanPostProcessor通过设置该order属性来控制这些实例的运行顺序。仅当BeanPostProcessor实现Ordered 接口时才能设置此属性。如果您自己编写BeanPostProcessor,您也应该考虑实现该Ordered接口。

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

BeanPostProcessor实例的范围是每个容器。仅当您使用容器层次结构时,这才是相关的。如果您BeanPostProcessor在一个容器中定义 a ,它只会对该容器中的 bean 进行后处理。换句话说,在一个容器中定义的 bean 不会由BeanPostProcessor另一个容器中定义的 bean 进行后处理,即使两个容器都是同一层次结构的一部分。

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

该org.springframework.beans.factory.config.BeanPostProcessor接口正好包含两个回调方法。当这样的类注册为容器的后处理器时,对于容器创建的每个 bean 实例,后处理器都会在容器初始化方法(例如InitializingBean.afterPropertiesSet()或任何声明的init方法)之前从容器中获取回调调用,并在任何 bean 初始化回调之后。后处理器可以对 bean 实例执行任何操作,包括完全忽略回调。bean 后处理器通常检查回调接口,或者它可以用代理包装 bean。一些 Spring AOP 基础设施类被实现为 bean 后处理器,以提供代理包装逻辑。

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

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

以编程方式注册BeanPostProcessor实例

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

BeanPostProcessor 实例和 AOP 自动代理

实现BeanPostProcessor接口的类是特殊的,容器会对其进行不同的处理。BeanPostProcessor它们直接引用的所有实例和 bean 在启动时被实例化,作为ApplicationContext. 接下来,所有BeanPostProcessor实例都以排序方式注册并应用于容器中的所有其他 bean。由于 AOP 自动代理是作为BeanPostProcessor自身实现的,BeanPostProcessor 因此它们直接引用的实例和 bean 都不符合自动代理的条件,因此,它们没有编织方面。

对于任何此类 bean,您应该看到一条信息性日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

如果您BeanPostProcessor通过使用自动装配或@Resource(可能回退到自动装配)将 bean 连接到您的 bean ,则 Spring 在搜索类型匹配依赖项候选者时可能会访问意外的 bean,因此,使它们不符合自动代理或其他类型的 bean 发布的条件-加工。例如,如果您有一个依赖项,@Resource其中字段或 setter 名称不直接对应于 bean 的声明名称并且没有使用 name 属性,则 Spring 访问其他 bean 以按类型匹配它们。

以下示例展示了如何BeanPostProcessor在ApplicationContext.

示例:Hello World, BeanPostProcessor-style

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

以下清单显示了自定义BeanPostProcessor实现类定义:

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;
    }
}

以下beans元素使用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 动态语言支持在标题为动态语言支持的章节中有详细介绍 。)

以下 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“信使”:org.springframework.scripting.groovy.GroovyMessenger@272961 
org.springframework.scripting.groovy.GroovyMessenger@272961

示例: AutowiredAnnotationBeanPostProcessor

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

1.2 自定义配置元数据BeanFactoryPostProcessor

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

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

如果您想更改实际的 bean 实例(即从配置元数据创建的对象),那么您需要使用 a BeanPostProcessor (前面在使用 a 自定义 Bean 中进行了描述BeanPostProcessor)。虽然技术上可以在 a 中使用 bean 实例BeanFactoryPostProcessor(例如,通过使用 BeanFactory.getBean()),但这样做会导致过早的 bean 实例化,违反标准的容器生命周期。这可能会导致负面影响,例如绕过 bean 后处理。

此外,BeanFactoryPostProcessor实例的范围是每个容器。这仅在您使用容器层次结构时才相关。如果您BeanFactoryPostProcessor在一个容器中定义 a ,则它仅应用于该容器中的 bean 定义。一个容器中的 Bean 定义不会由BeanFactoryPostProcessor另一个容器中的实例进行后处理,即使两个容器属于同一层次结构的一部分。

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

一个ApplicationContext自动检测部署在它实现了任何豆BeanFactoryPostProcessor接口。它在适当的时候使用这些 bean 作为 bean factory 后处理器。您可以像部署任何其他 bean 一样部署这些后处理器 bean。

与BeanPostProcessors 一样,您通常不希望将BeanFactoryPostProcessors配置 为延迟初始化。如果没有其他 bean 引用 a Bean(Factory)PostProcessor,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,并且Bean(Factory)PostProcessor即使您在元素的声明中将该default-lazy-init属性设置为,也 将被急切地实例化 。 true
示例:类名替换 PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer标准 JavaProperties格式将属性值从一个单独的文件中的 bean 定义外部化。这样做使部署应用程序的人员能够自定义特定于环境的属性,例如数据库 URL 和密码,而无需修改容器的主要 XML 定义文件或文件的复杂性或风险。

考虑以下基于 XML 的配置元数据片段,其中DataSource 定义了一个with 占位符值:

<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文件配置的属性。在运行时,aPropertySourcesPlaceholderConfigurer应用于替换数据源的某些属性的元数据。要替换的值被指定为表单的占位符${property-name},它遵循 Ant 和 log4j 以及 JSP EL 样式。

实际值来自另一个标准 JavaProperties格式的文件:

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

因此,该${jdbc.username}字符串在运行时替换为值“sa”,同样适用于与属性文件中的键匹配的其他占位符值。在PropertySourcesPlaceholderConfigurer为大多数属性和bean定义的属性占位符检查。此外,您可以自定义占位符前缀和后缀。

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

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

在PropertySourcesPlaceholderConfigurer不仅将查找在属性Properties 指定的文件。默认情况下,如果在指定的属性文件中找不到属性,它会检查 SpringEnvironment属性和常规 JavaSystem属性。

您可以使用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}"/>

如果该类在运行时无法解析为有效类,则在创建 bean 时解析该 bean 失败,这是在非延迟初始化 beanpreInstantiateSingletons() 的 an 阶段ApplicationContext。

示例: PropertyOverrideConfigurer

在PropertyOverrideConfigurer另一个bean工厂后处理器,类似 PropertySourcesPlaceholderConfigurer,但不同的是后者,原来的定义可以在所有的bean属性有缺省值或者根本没有值。如果覆盖 Properties文件没有特定 bean 属性的条目,则使用默认上下文定义。

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

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

beanName.property=value

以下清单显示了该格式的示例:

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

此示例文件可与包含名为dataSource具有driver和url属性的 bean 的容器定义一起使用 。

还支持复合属性名称,只要路径的每个组件除了被覆盖的最终属性之外都已经是非空的(大概是由构造函数初始化的)。在以下示例中,bean的sammy属性的bob属性的fred属性tom设置为标量值123:

tom.fred.bob.sammy=123

指定的覆盖值始终是文字值。它们不会被翻译成 bean 引用。当 XML bean 定义中的原始值指定 bean 引用时,此约定也适用。
使用contextSpring 2.5 中引入的命名空间,可以使用专用配置元素配置属性覆盖,如以下示例所示:

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

2.3 自定义实例化逻辑FactoryBean

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

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

该FactoryBean接口提供了三种方法:

T getObject():返回此工厂创建的对象的实例。实例可能会被共享,这取决于这个工厂是返回单例还是原型。

boolean isSingleton():true如果FactoryBean返回单例或false其他情况,则返回 。此方法的默认实现返回true.

Class<?> getObjectType(): 返回getObject()方法返回的对象类型或者null如果类型事先未知。

这个FactoryBean概念和接口在 Spring 框架中的很多地方都有使用。FactoryBean接口的50 多个实现与 Spring 本身一起提供。

当你需要问一个容器实际FactoryBean实例本身,而不是它创建的bean,前缀bean的id用连字符(&调用时)getBean()的方法ApplicationContext。因此,对于FactoryBean 具有idof的给定myBean,调用getBean(“myBean”)容器返回 的乘积FactoryBean,而调用getBean("&myBean")返回 FactoryBean实例本身。

猜你喜欢

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