容器扩展点
通常,应用程序开发人员不需要对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
会自动检测在配置元数据中定义的实现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自动代理
实现 对于任何这样的bean,您应该看到一个信息日志消息: 如果您通过自动装配或 |
以下示例展示了如何在ApplicationContext
中编写、注册和使用BeanPostProcessor
实例。
示例:你好,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>
<!--
当上述bean(messenger)被实例化时,这个自定义的
BeanPostProcessor实现将把这个事实输出到系统控制台
-->
<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
将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
属性。
对于给定应用程序,应仅定义一个此类元素,其中包含它所需的属性。只要具有不同的占位符语法( 如果需要将用于替换的属性源模块化,不应创建多个属性占位符。相反,应创建自己的 |
您可以使用
如果在运行时无法将类解析为有效类,则在创建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
的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 Framework中的许多地方使用。Spring本身附带了50多个FactoryBean
接口的实现。
当您需要向容器请求实际的FactoryBean
实例而不是它生成的bean时,在调用ApplicationContext
的getBean()
方法时,将bean的id
前缀设为与符号(&
)。因此,对于具有id
为myBean
的给定FactoryBean
,在容器上调用getBean("myBean")
将返回FactoryBean
的产品,而调用getBean("&myBean")
将返回FactoryBean
实例本身。