跳到主要内容
版本:7.0.3

容器扩展点

Hunyuan 7b 中英对照 Container Extension Points

通常,应用程序开发人员不需要继承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 注册为后处理器(post-processors),以便在 Bean 创建之后能够调用它们。Bean 后处理器的部署方式与其他 Bean 一致。

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

备注

以编程方式注册BeanPostProcessor实例

虽然推荐的方法是通过ApplicationContext的自动检测来注册BeanPostProcessor(如前所述),但也可以通过使用addBeanPostProcessor方法,以编程方式针对ConfigurableBeanFactory进行注册。当需要在注册之前评估条件逻辑,或者甚至需要在层次结构中的不同上下文之间复制BeanPostProcessor时,这种方法会非常有用。不过需要注意的是,以编程方式添加的BeanPostProcessor实例并不遵循Ordered接口的规定。在这种情况下,执行顺序是由注册顺序决定的。另外还要注意的是,无论是否有明确的顺序要求,以编程方式注册的BeanPostProcessor实例总是会先于通过自动检测注册的实例被执行。

备注

BeanPostProcessor实例与AOP自动代理

实现BeanPostProcessor接口的类具有特殊性,容器会对它们进行不同的处理。所有BeanPostProcessor实例以及它们直接引用的bean会在应用启动时被实例化,这是ApplicationContext特殊启动阶段的一部分。随后,所有BeanPostProcessor实例会以排序的方式被注册,并应用于容器中的其他bean。由于AOP自动代理本身就是通过BeanPostProcessor来实现的,因此BeanPostProcessor实例及其直接引用的bean都不符合自动代理的条件,也就不会被编织(即不会应用AOP方面)到这些bean上。

对于这类bean,你可能会看到一条信息日志:Bean someBean 不符合所有BeanPostProcessor接口的处理条件(例如:不符合自动代理的条件)

如果你通过自动装配或@Resource(在某些情况下可能会回退为自动装配)将其他bean与BeanPostProcessor关联起来,那么在Spring寻找类型匹配的依赖候选者时,它可能会访问到非预期的bean,从而导致这些bean不再符合自动代理或其他类型的bean后处理条件。例如,如果你有一个带有@Resource注解的依赖,但其字段或setter名称与声明的bean名称并不直接对应,且没有使用name属性,那么Spring会通过类型来匹配其他bean。

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

示例:Hello World,BeanPostProcessor 风格

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

以下列表显示了自定义的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。)

以下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 '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接口的Java文档。

备注

如果你想修改实际的bean实例(即从配置元数据中创建的对象),那么你需要使用BeanPostProcessor(在通过使用BeanPostProcessor自定义Bean中有详细描述)。虽然从技术上讲可以在BeanFactoryPostProcessor内部操作bean实例(例如,使用BeanFactory.getBean()),但这样做会导致bean提前实例化,从而违反标准容器生命周期。这可能会产生负面副作用,比如绕过bean的后处理步骤。

此外,BeanFactoryPostProcessor的实例是按容器 scope 的。只有在使用容器层次结构时,这一点才相关。如果你在一个容器中定义了BeanFactoryPostProcessor,它仅适用于该容器中的bean定义。即使两个容器属于同一层次结构,一个容器中的bean定义也不会被另一个容器中的BeanFactoryPostProcessor实例进行后处理。

当bean工厂后处理器在ApplicationContext内部被声明时,它会被自动运行,以便对定义容器的配置元数据进行修改。Spring包含了许多预定义的bean工厂后处理器,例如PropertyOverrideConfigurerPropertySourcesPlaceholderConfigurer。你也可以使用自定义的BeanFactoryPostProcessor——例如,用于注册自定义的属性编辑器。

ApplicationContext 会自动检测其中部署的实现了 BeanFactoryPostProcessor 接口的 bean。在适当的时候,它会使用这些 bean 作为 bean 工厂的后处理器(bean factory post-processors)。你可以像部署其他 bean 一样来部署这些后处理器 bean。

备注

BeanPostProcessor类似,通常也不建议为BeanFactoryPostProcessor配置延迟初始化。如果没有其他bean引用到Bean(Factory)PostProcessor,那么该后处理器将根本不会被实例化。因此,即使你在<beans />元素的声明上将default-lazy-init属性设置为true,标记其进行延迟初始化也会被忽略,Bean(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"/>

PropertySources PlaceholderConfigurer不仅会在你指定的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 时解析就会失败,这一过程发生在 ApplicationContextpreInstantiateSingletons() 阶段(对于非懒加载初始化的 bean)。

示例:PropertyOverrideConfigurer

PropertyOverrideConfigurer是另一个bean工厂后处理器,它与PropertySourcesPlaceholderConfigurer类似,但不同之处在于:对于bean属性来说,原始定义可以有默认值,也可以完全没有值。如果覆盖的Properties文件中某bean属性没有对应条目,那么就会使用上下文中的默认定义。

请注意,bean定义本身并不知道自己被重写了,因此从XML定义文件中并不能立即看出正在使用的是重写配置器(override configurer)。如果存在多个PropertyOverrideConfigurer实例为同一个bean属性定义了不同的值,那么根据重写机制,最后定义的那个值将会被采用。

属性文件配置行的格式如下:

beanName.property=value

以下列表展示了一个格式的示例:

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

这个示例文件可以与一个容器定义一起使用,该容器定义中包含一个名为 dataSource 的 Bean,该 Bean 具有 driverClassNameurl 属性。

也支持复合属性名,只要路径中的每个组件(除了被覆盖的最后一个属性)都不为null(假定已经由构造函数初始化)。在以下示例中,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>接口提供了三种方法:

  • getObject(): 返回此工厂创建的对象的实例。根据该工厂是返回单例(singletons)还是原型(prototypes),这个实例可能是可共享的。

  • boolean isSingleton(): 如果该 FactoryBean 返回单例,则返回 true,否则返回 false。此方法的默认实现返回 true

  • Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型;如果类型事先未知,则返回 null

FactoryBean概念和接口在Spring框架的多个地方都有使用。Spring本身就提供了超过50种FactoryBean接口的实现。

当你需要从容器中获取FactoryBean实例本身,而不是它所生产的bean时,在调用ApplicationContextgetBean()方法时,需要在bean的id前加上&符号。因此,对于一个idmyBeanFactoryBean,调用getBean("myBean")会返回该FactoryBean生产的bean,而调用getBean("&myBean")则会返回FactoryBean实例本身。