Container Extension Points
通常,应用程序开发人员不需要对 ApplicationContext
实现类进行子类化。相反,Spring IoC 容器可以通过插入特殊集成接口的实现来进行扩展。接下来的几个部分描述了这些集成接口。
Customizing Beans by Using a BeanPostProcessor
BeanPostProcessor
接口定义了你能够实现的回调方法,以便提供你自己的(或覆盖容器的默认)实例化逻辑、依赖解析逻辑等等。如果你想要在 Spring 容器完成一个 bean 的实例化、配置和初始化之后实现一些自定义逻辑,你可以插入一个或多个自定义的 BeanPostProcessor
实现。
你可以配置多个 BeanPostProcessor
实例,并且你可以通过设置 order
属性来控制这些 BeanPostProcessor
实例运行的顺序。如果 BeanPostProcessor
实现了 Ordered
接口,你只能设置此属性。如果你编写自己的 BeanPostProcessor
,你应该考虑也实现 Ordered
接口。有关更多详细信息,请参阅 BeanPostProcessor
和 Ordered
接口的 javadoc。另请参阅 programmatic registration of BeanPostProcessor
instances 中的注释。
|
org.springframework.beans.factory.config.BeanPostProcessor
接口恰好由两个回调方法组成。当此类作为后处理器向容器注册时,对于容器创建的每个 bean 实例,该后处理器都会在调用容器初始化方法(例如 InitializingBean.afterPropertiesSet()
或任何已声明的 init
方法)之前和在任何 bean 初始化回调之后从容器中获得一个回调。该后处理器可以对 bean 实例采取任何操作,包括完全忽略该回调。一个 bean 后处理器通常会检查回调接口,或者它可能会使用代理包装一个 bean。某些 Spring AOP 基础架构类被实现为 bean 后处理器,以便提供代理包装逻辑。
ApplicationContext
会自动检测在配置元数据中定义的所有实现 BeanPostProcessor
接口的 bean。ApplicationContext
将这些 bean 注册为后处理器,以便稍后在创建 bean 时对它们进行调用。可以像部署任何其他 bean 一样在容器中部署 bean 后处理器。
请注意,在配置类中使用 @Bean
工厂方法声明 BeanPostProcessor
时,工厂方法的返回类型应为实现类本身或至少为 org.springframework.beans.factory.config.BeanPostProcessor
接口,明确指示该 bean 的后处理器性质。否则,ApplicationContext
在完全创建该 bean 之前无法通过类型自动检测到它。由于必须尽早实例化 BeanPostProcessor
以便对其应用上下文中的其他 bean 的初始化,因此这种早期类型检测至关重要。
Programmatically registering
BeanPostProcessor instances尽管 @{13} 注册的推荐方法是通过 @{14} 自动检测(如前文所述),但你可以使用 @{16} 方法对照一个 @{15} 以编程方式注册它们。这在需要在注册前评估条件逻辑或即使在层次结构中的上下文间复制 bean 后置处理器时很有用。但是请注意,以编程方式添加的 @{17} 实例并不遵守 @{18} 接口。这里,执行顺序由注册顺序决定。另请注意,以编程方式注册的 @{19} 实例总是比通过自动检测注册的实例先处理,而不管显式排序如何。 |
BeanPostProcessor instances and AOP auto-proxying实现 |
下面的示例展示了如何在 ApplicationContext
中编写、注册和使用 BeanPostProcessor
实例。
Example: Hello World, BeanPostProcessor
-style
第一个示例说明了基本用法。该示例显示了一个自定义的 BeanPostProcessor
实现,它调用容器创建的每个 bean 的 toString()
方法,并将结果字符串打印到系统控制台上。
下面的列表展示了自定义 BeanPostProcessor
实现类定义:
-
Java
-
Kotlin
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;
}
}
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 动态语言支持在题为 Dynamic Language Support的章节中进行了详细说明。)
以下 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
Customizing Configuration Metadata with a 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。
与 @{38} 一样,该后处理器根本不会实例化。因此,将其标记为惰性初始化将被忽略,即使你在 @{42} 元素的声明中将 @{40} 属性设置为 @{41},@{39} 也将立即实例化。 |
Example: The Class Name Substitution 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
文件配置的属性。在运行时,会对替换 DataSource
的某些属性的元数据应用 PropertySourcesPlaceholderConfigurer
。要替换的值指定为 ${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,用于收集要使用的属性。
您可以使用
如果类在运行时不能解析为有效的类,则当 Bean 即将创建时(对于非延迟初始化 Bean,是在 |
Example: The 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 具有 driverClassName
和 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"/>
Customizing Instantiation Logic with a 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 Framework 中的多个位置使用。FactoryBean
接口有 50 多个实现与 Spring 本身一起提供。
当您需要向容器请求实际的 FactoryBean
实例而不是它生成 bean 时,在调用 ApplicationContext
的 getBean()
方法时,使用 &
符号为 bean 的 id
加前缀。因此,对于具有 id
为 myBean
的给定的 FactoryBean
,对容器调用 getBean("myBean")
会返回 FactoryBean
的产品,而调用 getBean("&myBean")
会返回 FactoryBean
实例本身。