容器扩展点

通常,应用程序开发人员不需要子类化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。一些 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 的声明名称不直接对应,并且未使用任何名称属性,则 Spring 会访问其他 Bean 以通过类型匹配它们。

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

示例:Hello World,BeanPostProcessor风格

此第一个示例说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,它在容器创建每个 Bean 时调用每个 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。Spring动态语言支持在名为动态语言支持的章节中详细介绍。)

下面的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读取配置元数据并在容器实例化任何其他bean(除了BeanFactoryPostProcessor实例)之前对其进行更改。

您可以配置多个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将bean定义中的属性值从单独的文件中外部化,方法是使用标准Java Properties格式。这样做使应用程序部署人员可以自定义特定于环境的属性,例如数据库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定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。

使用Spring 2.5中引入的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之前(对于非lazy-init bean,则在ApplicationContextpreInstantiateSingletons()阶段)bean的解析将失败。

示例:PropertyOverrideConfigurer

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

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

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

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前加上与符号 (&)。因此,对于给定的idmyBeanFactoryBean,在容器上调用getBean("myBean")会返回FactoryBean的产品,而调用getBean("&myBean")会返回FactoryBean实例本身。