容器扩展点
通常,应用程序开发人员不需要对 ApplicationContext
实现类进行子类化。相反,可以通过插入特殊集成接口的实现来扩展 Spring IoC 容器。以下几节介绍了这些集成接口。
使用 BeanPostProcessor
自定义 Bean
BeanPostProcessor
接口定义了回调方法,你可以实现这些方法来提供你自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑等。如果你希望在 Spring 容器完成实例化、配置和初始化 Bean 之后实现一些自定义逻辑,你可以插入一个或多个自定义 BeanPostProcessor
实现。
你可以配置多个 BeanPostProcessor
实例,并且可以通过设置 order
属性来控制这些 BeanPostProcessor
实例运行的顺序。仅当 BeanPostProcessor
实现 Ordered
接口时,你才能设置此属性。如果你编写了自己的 BeanPostProcessor
,你也应该考虑实现 Ordered
接口。有关更多详细信息,请参阅 BeanPostProcessor
和 Ordered
接口的 javadoc。另请参阅有关 BeanPostProcessor
实例的编程注册 的说明。
要更改实际的 bean 定义(即定义 bean 的蓝图),你需要使用 |
org.springframework.beans.factory.config.BeanPostProcessor
接口恰好包含两个回调方法。当这样的类作为后处理器向容器注册时,对于容器创建的每个 bean 实例,后处理器都会在容器初始化方法(例如 InitializingBean.afterPropertiesSet()
或任何声明的 init
方法)被调用之前和任何 bean 初始化回调之后从容器获取回调。后处理器可以对 bean 实例执行任何操作,包括完全忽略回调。bean 后处理器通常检查回调接口,或者它可能会使用代理包装 bean。一些 Spring AOP 基础设施类被实现为 bean 后处理器,以便提供代理包装逻辑。
ApplicationContext
会自动检测在配置元数据中定义的任何 bean,这些 bean 实现 BeanPostProcessor
接口。ApplicationContext
将这些 bean 注册为后处理器,以便在创建 bean 时稍后调用它们。bean 后处理器可以与任何其他 bean 以相同的方式部署在容器中。
请注意,当使用配置类上的 @Bean
工厂方法声明 BeanPostProcessor
时,工厂方法的返回类型应该是实现类本身或至少是 org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚地表明该 bean 的后处理器性质。否则,ApplicationContext
在完全创建它之前无法按类型自动检测它。由于 BeanPostProcessor
需要在上下文中初始化其他 bean 时尽早实例化,因此这种早期类型检测至关重要。
以编程方式注册
虽然推荐通过 BeanPostProcessor 实例ApplicationContext 自动检测(如前所述)的方式注册 BeanPostProcessor ,但你可以使用 addBeanPostProcessor 方法针对 ConfigurableBeanFactory 以编程方式注册它们。这在需要在注册前评估条件逻辑时或甚至在层次结构中跨上下文复制 Bean 后置处理器时很有用。但是,请注意,以编程方式添加的 BeanPostProcessor 实例不遵循 Ordered 接口。此处,注册顺序决定了执行顺序。另请注意,以编程方式注册的 BeanPostProcessor 实例始终在通过自动检测注册的实例之前处理,无论任何显式排序如何。
|
BeanPostProcessor 实例和 AOP 自动代理实现 对于任何此类 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。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
使用 BeanFactoryPostProcessor
自定义配置元数据
我们接下来要了解的扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor
。此接口的语义与 BeanPostProcessor
的语义类似,但有一个主要区别:BeanFactoryPostProcessor
操作 bean 配置元数据。也就是说,Spring IoC 容器允许 BeanFactoryPostProcessor
读取配置元数据,并在容器实例化除 BeanFactoryPostProcessor
实例之外的任何 bean 之前 更改它。
你可以配置多个 BeanFactoryPostProcessor
实例,并且可以通过设置 order
属性来控制这些 BeanFactoryPostProcessor
实例的运行顺序。但是,只有当 BeanFactoryPostProcessor
实现 Ordered
接口时,你才能设置此属性。如果你编写自己的 BeanFactoryPostProcessor
,则也应该考虑实现 Ordered
接口。有关更多详细信息,请参阅 BeanFactoryPostProcessor
和 Ordered
接口的 javadoc。
如果你想更改实际的 bean 实例(即从配置元数据创建的对象),那么你需要使用 此外, |
当在 ApplicationContext
中声明 Bean 工厂后置处理器时,它会自动运行,以便对定义容器的配置元数据应用更改。Spring 包含许多预定义的 Bean 工厂后置处理器,例如 PropertyOverrideConfigurer
和 PropertySourcesPlaceholderConfigurer
。您还可以使用自定义 BeanFactoryPostProcessor
,例如,注册自定义属性编辑器。
ApplicationContext
会自动检测部署到其中并实现 BeanFactoryPostProcessor
接口的任何 Bean。它在适当的时候将这些 Bean 用作 Bean 工厂后置处理器。您可以像部署任何其他 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 定义的大多数属性和属性中的占位符。此外,您可以自定义占位符前缀和后缀。
在 Spring 2.5 中引入的 context
命名空间中,您可以使用专用配置元素配置属性占位符。您可以在 location
属性中以逗号分隔列表的形式提供一个或多个位置,如下例所示
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不仅在您指定的 Properties
文件中查找属性。默认情况下,如果它在指定的属性文件中找不到属性,它会检查 Spring Environment
属性和常规 Java System
属性。
对于具有所需属性的给定应用程序,只应定义一个这样的元素。只要具有不同的占位符语法( 如果您需要模块化用于替换的属性源,则不应创建多个属性占位符。相反,您应该创建自己的 |
您可以使用
如果在运行时无法将类解析为有效类,则在准备创建 bean 时,bean 解析将失败,对于非延迟初始化 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
的 bean 的容器定义一起使用,该 bean 具有 driver
和 url
属性。
复合属性名称也受支持,只要路径的每个组件(除了要覆盖的最终属性)都已非空(可能由构造函数初始化)。在以下示例中,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 时,在调用 ApplicationContext
的 getBean()
方法时,用符号(&
)给 Bean 的 id
加前缀。因此,对于给定的 id
为 myBean
的 FactoryBean
,在容器上调用 getBean("myBean")
将返回 FactoryBean
的产品,而调用 getBean("&myBean")
将返回 FactoryBean
实例本身。