容器扩展点

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

通过使用 BeanPostProcessor 自定义 Bean

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

您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的运行顺序。只有当 BeanPostProcessor 实现 Ordered 接口时,您才能设置此属性。如果您编写自己的 BeanPostProcessor,您也应该考虑实现 Ordered 接口。有关更多详细信息,请参阅 BeanPostProcessorOrdered 接口的 Javadoc。另请参阅关于 BeanPostProcessor 实例的编程式注册 的说明。

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

BeanPostProcessor 实例是按容器作用域的。这仅在您使用容器层次结构时才相关。如果您在一个容器中定义一个 BeanPostProcessor,它只对该容器中的 Bean 进行后处理。换句话说,在一个容器中定义的 Bean 不会被另一个容器中定义的 BeanPostProcessor 后处理,即使这两个容器是同一层次结构的一部分。

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

org.springframework.beans.factory.config.BeanPostProcessor 接口由两个回调方法组成。当此类被注册为容器的后处理器时,对于容器创建的每个 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 is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)

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

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

示例:Hello World,BeanPostProcessor 风格

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

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

  • Java

  • Kotlin

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

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

class InstantiationTracingBeanPostProcessor : BeanPostProcessor {

	// simply return the instantiated bean as-is
	override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
		return bean // we could potentially return any object reference here...
	}

	override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
		println("Bean '$beanName' created : $bean")
		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。)

以下 Java 应用程序运行上述代码和配置

  • Java

  • Kotlin

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

}
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
	val messenger = ctx.getBean<Messenger>("messenger")
	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 方法和任意配置方法。

使用 BeanFactoryPostProcessor 自定义配置元数据

我们接下来要看的扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。该接口的语义与 BeanPostProcessor 相似,但有一个主要区别:BeanFactoryPostProcessor 操作的是 Bean 配置元数据。也就是说,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 实例后处理,即使这两个容器是同一层次结构的一部分。

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

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

BeanPostProcessor 类似,您通常不希望将 BeanFactoryPostProcessor 配置为延迟初始化。如果没有其他 Bean 引用 Bean(Factory)PostProcessor,则该后处理器将完全不会被实例化。因此,将其标记为延迟初始化将被忽略,并且即使您在 <beans /> 元素的声明上将 default-lazy-init 属性设置为 trueBean(Factory)PostProcessor 也将被急切实例化。

示例:使用 PropertySourcesPlaceholderConfigurer 进行属性占位符替换

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

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

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

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<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 应用于元数据,替换 DataSource 的一些属性。要替换的值以 ${property-name} 形式的占位符指定,这遵循 Ant、log4j 和 JSP EL 样式。

实际值来自另一个标准 Java Properties 格式的文件

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

因此,${jdbc.username} 字符串在运行时被替换为值 'sa',其他与属性文件中的键匹配的占位符值也同样适用。PropertySourcesPlaceholderConfigurer 检查 Bean 定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀、后缀、默认值分隔符和转义字符。此外,可以通过 JVM 系统属性(或通过 SpringProperties 机制)设置 spring.placeholder.escapeCharacter.default 属性来全局更改或禁用默认转义字符。

使用 context 命名空间,您可以使用专用的配置元素来配置属性占位符。您可以在 location 属性中提供一个或多个位置作为逗号分隔列表,如以下示例所示

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

PropertySourcesPlaceholderConfigurer 不仅在您指定的 Properties 文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它会对照 Spring Environment 属性和常规 Java System 属性进行检查。

对于给定的应用程序,应该只定义一个这样的元素,并包含它所需的属性。只要它们具有不同的占位符语法(${…​}),就可以配置多个属性占位符。

如果您需要将用于替换的属性源模块化,则不应创建多个属性占位符。相反,您应该创建自己的 PropertySourcesPlaceholderConfigurer Bean,它会收集要使用的属性。

您可以使用 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,在 ApplicationContextpreInstantiateSingletons() 阶段),Bean 的解析将失败。

示例: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 且具有 driverClassNameurl 属性的 Bean 的容器定义一起使用。

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

tom.fred.bob.sammy=123
指定的覆盖值始终是字面值。它们不会转换为 Bean 引用。此约定也适用于 XML Bean 定义中的原始值指定 Bean 引用的情况。

Spring 2.5 中引入的 context 命名空间允许使用专用配置元素配置属性覆盖,如以下示例所示

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

使用 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 框架的许多地方都有使用。Spring 本身就包含了 50 多个 FactoryBean 接口的实现。

当您需要向容器请求实际的 FactoryBean 实例本身而不是它生成的 Bean 时,在调用 ApplicationContextgetBean() 方法时,请在 Bean 的 id 前加上 & 符号(&)。因此,对于一个 idmyBean 的给定 FactoryBean,在容器上调用 getBean("myBean") 返回 FactoryBean 的产品,而调用 getBean("&myBean") 返回 FactoryBean 实例本身。

© . This site is unofficial and not affiliated with VMware.