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

容器扩展点

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

通过使用 BeanPostProcessor 自定义 Bean

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

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

一个 ApplicationContext 会自动检测配置元数据中定义的任何实现 BeanPostProcessor 接口的 beans。该 ApplicationContext 会将这些 beans 注册为后处理器,以便在 bean 创建时调用。bean 后处理器可以以与其他 beans 相同的方式部署到容器中。spring-doc.cadn.net.cn

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

以编程方式注册 BeanPostProcessor 个实例
虽然推荐的方法是BeanPostProcessor注册是通过ApplicationContext自动检测(如前所述),您可以将它们编程方式注册到一个ConfigurableBeanFactory通过使用addBeanPostProcessor方法。这在你需要注册前评估条件逻辑时很有用,甚至可以在层次结构中的不同上下文之间复制 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

如果您通过使用自动装配或BeanPostProcessor(可能会回退到自动装配)将 bean 连接到您的BeanPostProcessor中,Spring 在搜索类型匹配的依赖项候选时可能会访问意外的 bean,从而使其无法用于自动代理或其他类型的 bean 后处理。例如,如果您有一个用@Resource注解的依赖项,其中字段或 setter 名称不直接对应于 bean 的声明名称,并且没有使用 name 属性,Spring 会通过类型来匹配其他 bean。spring-doc.cadn.net.cn

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

示例:Hello World,BeanPostProcessor样式

第一个示例说明了基本用法。该示例显示了一个自定义 BeanPostProcessor 实现,在容器创建每个 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工厂后处理器,以对定义容器的配置元数据进行更改。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,那么该后处理器根本不会被实例化。 因此,将其标记为延迟初始化将被忽略,即使你在声明 <beans /> 元素时将 default-lazy-init 属性设置为 trueBean(Factory)PostProcessor 也会被立即实例化。

示例:类名替换 PropertySourcesPlaceholderConfigurer

您可以使用 PropertySourcesPlaceholderConfigurer 通过使用标准 Java Properties 格式,将 bean 定义中的属性值外部化到单独的文件中。 这样做的好处是,部署应用程序的人可以在不修改容器的主要 XML 定义文件或文件的情况下,自定义特定于环境的属性,例如数据库 URL 和密码,从而避免复杂性或风险。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时(即对于非延迟初始化的bean,在preInstantiateSingletons()阶段)会失败。spring-doc.cadn.net.cn

示例: PropertyOverrideConfigurer

PropertyOverrideConfigurer,另一个 bean 工厂后处理器,类似于 PropertySourcesPlaceholderConfigurer,但与后者不同的是,原始定义可以为 bean 属性提供默认值或根本没有值。如果覆盖的 Properties 文件中没有某个 bean 属性的条目,则使用默认上下文定义。spring-doc.cadn.net.cn

请注意,bean定义并不知道它已被覆盖,因此从XML定义文件中无法立即看出正在使用覆盖配置器。如果多个<code>0</code>实例为同一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

也支持复合属性名称,只要路径中除了要覆盖的最后一个属性外,其他每个组件都已经不是 null(可能是由构造函数初始化的)。在下面的例子中,tom bean 的 fred 属性的 bob 属性的 sammy 属性被设置为标量值 123spring-doc.cadn.net.cn

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

随着Spring 2.5中引入的<code>0</code>命名空间,可以通过专用的配置元素配置属性覆盖,如下例所示: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 容器实例化逻辑的可扩展点。如果您有复杂的初始化代码,与使用可能相当冗长的 XML 相比,更适合用 Java 表达,那么您可以创建自己的 FactoryBean,在该类中编写复杂的初始化代码,然后将自定义的 FactoryBean 插入到容器中。spring-doc.cadn.net.cn

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

  • T getObject(): 返回此工厂创建的对象的实例。根据此工厂返回单例或原型,该实例可能是共享的。spring-doc.cadn.net.cn

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

  • Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,或在事先不知道类型时返回 nullspring-doc.cadn.net.cn

FactoryBean 概念和接口在 Spring 框架的许多地方都有使用。Spring 本身提供了超过 50 个 FactoryBean 接口的实现。spring-doc.cadn.net.cn

当您需要让容器返回实际的 FactoryBean 实例本身,而不是它生成的 bean 时,在调用 getBean() 方法时,应将 bean 的 id 前缀加上与号符号(&)。因此,对于一个具有 FactoryBeanid,其 myBeangetBean("myBean"),在容器上调用 getBean("myBean") 会返回 FactoryBean 的结果,而调用 getBean("&myBean") 则会返回 FactoryBean 实例本身。spring-doc.cadn.net.cn