对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

容器扩展点

通常,应用程序开发人员不需要继承 ApplicationContext 的实现类。相反,可以通过插入特殊集成接口的实现来扩展 Spring IoC 容器。接下来的几节将介绍这些集成接口。spring-doc.cadn.net.cn

使用自定义 BeanBeanPostProcessor

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

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

BeanPostProcessor 实例作用于 bean(或对象)实例。也就是说,Spring IoC 容器会先实例化一个 bean 实例,然后 BeanPostProcessor 实例再执行它们的工作。spring-doc.cadn.net.cn

BeanPostProcessor 实例的作用域是每个容器独立的。这一点仅在您使用容器层级结构时才有意义。如果您在一个容器中定义了 BeanPostProcessor,它只会对该容器中的 bean 进行后处理。换句话说,即使两个容器属于同一层级结构,在一个容器中定义的 bean 也不会被另一个容器中定义的 BeanPostProcessor 进行后处理。spring-doc.cadn.net.cn

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

org.springframework.beans.factory.config.BeanPostProcessor 接口正好包含两个回调方法。当此类作为后处理器注册到容器中时,对于容器所创建的每个 bean 实例,该后处理器都会从容器接收到两次回调:一次在容器初始化方法(例如 InitializingBean.afterPropertiesSet() 或任何声明的 init 方法)被调用之前,另一次在所有 bean 初始化回调之后。后处理器可以对 bean 实例执行任意操作,包括完全忽略该回调。通常,bean 后处理器会检查回调接口,或者可能使用代理包装 bean。一些 Spring AOP 基础设施类就是作为 bean 后处理器实现的,以提供代理包装逻辑。spring-doc.cadn.net.cn

ApplicationContext 会自动检测配置元数据中定义的、实现了 BeanPostProcessor 接口的所有 bean。ApplicationContext 会将这些 bean 注册为后处理器,以便在创建 bean 时能够调用它们。Bean 后处理器可以像其他任何 bean 一样部署到容器中。spring-doc.cadn.net.cn

请注意,当通过配置类中的 BeanPostProcessor 工厂方法声明一个 @Bean 时,该工厂方法的返回类型应当是其实现类本身,或者至少是 org.springframework.beans.factory.config.BeanPostProcessor 接口,以明确表明该 bean 的后处理器特性。否则,ApplicationContext 将无法在完全创建该 bean 之前通过类型自动检测到它。由于 BeanPostProcessor 需要在上下文早期就被实例化,以便应用于上下文中其他 bean 的初始化过程,因此这种早期的类型检测至关重要。spring-doc.cadn.net.cn

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

实现 BeanPostProcessor 接口的类属于特殊类,容器会以不同的方式处理它们。所有 BeanPostProcessor 实例及其直接引用的 Bean 都会在启动时实例化,作为 ApplicationContext 特殊启动阶段的一部分。随后,所有 BeanPostProcessor 实例会以排序的方式注册,并应用于容器中所有后续的 Bean。由于 AOP 自动代理本身是通过一个 BeanPostProcessor 来实现的,因此 BeanPostProcessor 实例及其直接引用的 Bean 都不会被自动代理,因而也不会被织入切面。spring-doc.cadn.net.cn

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

如果你通过自动装配(autowiring)或 BeanPostProcessor(可能会回退到自动装配)将 bean 注入到你的 @Resource 中,Spring 在搜索类型匹配的依赖候选者时可能会访问到意外的 bean,从而导致这些 bean 无法被自动代理(auto-proxying)或进行其他类型的 bean 后置处理。例如,如果你有一个使用 @Resource 注解的依赖项,其字段名或 setter 方法名并未直接对应某个已声明 bean 的名称,并且未使用 name 属性,那么 Spring 会通过类型匹配去访问其他 bean。spring-doc.cadn.net.cn

以下示例展示了如何在 BeanPostProcessor 中编写、注册和使用 ApplicationContext 实例。spring-doc.cadn.net.cn

示例:你好,世界,BeanPostProcessor-style

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

以下清单展示了自定义 BeanPostProcessor 实现类的定义:spring-doc.cadn.net.cn

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 元素使用了 InstantiationTracingBeanPostProcessorspring-doc.cadn.net.cn

<?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 的动态语言支持详见题为动态语言支持的章节。)spring-doc.cadn.net.cn

以下 Java 应用程序运行了前面的代码和配置:spring-doc.cadn.net.cn

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

前述应用程序的输出类似于以下内容:spring-doc.cadn.net.cn

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

示例:AutowiredAnnotationBeanPostProcessor

结合使用回调接口或注解与自定义的 BeanPostProcessor 实现,是扩展 Spring IoC 容器的常用方式。一个例子是 Spring 自带的 AutowiredAnnotationBeanPostProcessor —— 这是一个随 Spring 发行版提供的 BeanPostProcessor 实现,用于自动装配带有注解的字段、setter 方法以及任意的配置方法。spring-doc.cadn.net.cn

使用自定义配置元数据BeanFactoryPostProcessor

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

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

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

此外,BeanFactoryPostProcessor 实例的作用域是每个容器独立的。这一点仅在你使用容器层级结构时才有意义。如果你在一个容器中定义了一个 BeanFactoryPostProcessor,它只会应用于该容器中的 bean 定义。即使两个容器属于同一层级结构,一个容器中的 bean 定义也不会被另一个容器中的 BeanFactoryPostProcessor 实例进行后处理。spring-doc.cadn.net.cn

当在 ApplicationContext 内部声明时,Bean 工厂后处理器(bean factory post-processor)会自动运行,以对定义容器的配置元数据进行修改。Spring 提供了多个预定义的 Bean 工厂后处理器,例如 PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。你也可以使用自定义的 BeanFactoryPostProcessor,例如用于注册自定义的属性编辑器。spring-doc.cadn.net.cn

ApplicationContext 会自动检测部署到其中的、实现了 BeanFactoryPostProcessor 接口的任何 bean。它会在适当的时机将这些 bean 用作 bean 工厂后处理器。你可以像部署其他任意 bean 一样部署这些后处理器 bean。spring-doc.cadn.net.cn

BeanPostProcessor一样,通常你不希望将BeanFactoryPostProcessor配置为延迟初始化。如果没有其他 bean 引用某个Bean(Factory)PostProcessor,那么该后置处理器根本不会被实例化。因此,即使你将其标记为延迟初始化,该设置也会被忽略,并且即使你在Bean(Factory)PostProcessor元素的声明中将default-lazy-init属性设置为true<beans />仍会被提前(急切地)实例化。

示例:类名替换PropertySourcesPlaceholderConfigurer

您可以使用 PropertySourcesPlaceholderConfigurer,通过标准的 Java Properties 格式,将 Bean 定义中的属性值外置到一个单独的文件中。 这样,部署应用程序的人员就可以自定义特定环境的属性(例如数据库 URL 和密码),而无需修改容器的主要 XML 定义文件,从而避免了由此带来的复杂性或风险。spring-doc.cadn.net.cn

考虑以下基于 XML 的配置元数据片段,其中定义了一个带有占位符值的 DataSourcespring-doc.cadn.net.cn

<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 的风格。spring-doc.cadn.net.cn

实际的值来自另一个采用标准 Java Properties 格式的文件:spring-doc.cadn.net.cn

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

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

通过 Spring 2.5 引入的 context 命名空间,您可以使用专用的配置元素来配置属性占位符。 您可以在 location 属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:spring-doc.cadn.net.cn

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

PropertySourcesPlaceholderConfigurer 不仅会在您指定的 Properties 文件中查找属性。默认情况下,如果在指定的属性文件中找不到某个属性,它还会检查 Spring 的 Environment 属性和标准的 Java System 属性。spring-doc.cadn.net.cn

对于一个给定的应用程序,应仅定义一个具有所需属性的此类元素。可以配置多个属性占位符,只要它们具有不同的占位符语法(${…​})即可。spring-doc.cadn.net.cn

如果你需要对用于替换的属性源进行模块化,你不应创建多个属性占位符。相反,你应该创建自己的PropertySourcesPlaceholderConfigurer bean 来收集要使用的属性。spring-doc.cadn.net.cn

你可以使用 PropertySourcesPlaceholderConfigurer 来替换类名,当你需要在运行时选择特定的实现类时,这有时会很有用。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<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 时解析会失败,对于非延迟初始化(non-lazy-init)的 bean,这发生在 preInstantiateSingletons()ApplicationContext 阶段。spring-doc.cadn.net.cn

示例:PropertyOverrideConfigurer

PropertyOverrideConfigurer 是另一种 Bean 工厂后置处理器,与 PropertySourcesPlaceholderConfigurer 类似。但与后者不同的是,原始定义中的 Bean 属性可以具有默认值,甚至完全不设置值。如果用于覆盖的 Properties 文件中没有包含某个 Bean 属性的条目,则使用默认的上下文定义。spring-doc.cadn.net.cn

请注意,bean 定义本身并不知道它被覆盖了,因此从 XML 定义文件中无法立即看出正在使用覆盖配置器(override configurer)。如果有多个 PropertyOverrideConfigurer 实例为同一个 bean 属性定义了不同的值,由于覆盖机制的作用,最后一个定义的值将生效。spring-doc.cadn.net.cn

属性文件的配置行采用以下格式:spring-doc.cadn.net.cn

beanName.property=value

以下列表展示了该格式的一个示例:spring-doc.cadn.net.cn

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

此示例文件可与包含名为 dataSource 的 bean 的容器定义一起使用,该 bean 具有 driverClassNameurl 属性。spring-doc.cadn.net.cn

也支持复合属性名称,只要路径中除最终要被覆盖的属性外,每个组成部分都已非空(推测是由构造函数初始化的)。在以下示例中,sammy bean 的 bob 属性的 fred 属性的 tom 属性被设置为标量值 123spring-doc.cadn.net.cn

tom.fred.bob.sammy=123
指定的覆盖值始终是字面量值。它们不会被转换为 bean 引用。即使 XML bean 定义中的原始值指定了一个 bean 引用,此约定也同样适用。

通过 Spring 2.5 引入的 context 命名空间,可以使用专用的配置元素来配置属性覆盖,如下例所示:spring-doc.cadn.net.cn

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

使用自定义实例化逻辑FactoryBean

你可以为本身是工厂的对象实现 org.springframework.beans.factory.FactoryBean 接口。spring-doc.cadn.net.cn

FactoryBean 接口是 Spring IoC 容器实例化逻辑的一个可插拔接入点。如果你有复杂的初始化代码,用 Java 表达比使用(可能非常冗长的)XML 更为合适,那么你可以创建自己的 FactoryBean,在该类中编写复杂的初始化逻辑,然后将自定义的 FactoryBean 插入到容器中。spring-doc.cadn.net.cn

FactoryBean<T> 接口提供了三个方法:spring-doc.cadn.net.cn

  • T getObject():返回此工厂所创建对象的一个实例。该实例可能是共享的,具体取决于此工厂返回的是单例(singleton)还是原型(prototype)。spring-doc.cadn.net.cn

  • boolean isSingleton():如果此 true 返回单例对象,则返回 FactoryBean,否则返回 false。该方法的默认实现返回 truespring-doc.cadn.net.cn

  • Class<?> getObjectType():返回 getObject() 方法所返回的对象类型,如果该类型事先未知,则返回 nullspring-doc.cadn.net.cn

FactoryBean 的概念和接口在 Spring 框架的许多地方都有使用。Spring 自带的 FactoryBean 接口实现就超过 50 种。spring-doc.cadn.net.cn

当您需要向容器请求实际的FactoryBean实例本身,而不是它生成的 bean 时,在调用ApplicationContextgetBean()方法时,需在 bean 的id前加上与符号(&)。因此,对于给定的FactoryBean,其idmyBean,在容器上调用getBean("myBean")将返回FactoryBean的产物,而调用getBean("&myBean")则返回FactoryBean实例本身。spring-doc.cadn.net.cn