容器扩展点
通常,应用程序开发人员不需要继承 ApplicationContext 实现类。
相反,可以通过插入特殊的集成接口实现来扩展 Infra IoC 容器。
接下来的几节将描述这些集成接口。
使用 BeanPostProcessor 自定义 Bean
BeanPostProcessor 接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器默认的)实例化逻辑、依赖项解析逻辑等。
如果您想在 Infra 容器完成实例化、配置和初始化 bean 之后实现一些自定义逻辑,您可以插入一个或多个自定义 BeanPostProcessor 实现。
您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的运行顺序。
只有当 BeanPostProcessor 实现了 Ordered 接口时,您才能设置此属性。
如果您编写自己的 BeanPostProcessor,您也应该考虑实现 Ordered 接口。
有关更多详细信息,请参阅 BeanPostProcessor
和 Ordered 接口的 javadoc。
另请参阅关于 以编程方式注册 BeanPostProcessor 实例 的说明。
|
要更改实际的 bean 定义(即定义 bean 的蓝图),您需要使用 |
infra.beans.factory.config.BeanPostProcessor 接口恰好由两个回调方法组成。
当这样一个类作为后处理器注册到容器时,对于容器创建的每个 bean 实例,后处理器都会在容器初始化方法(如 InitializingBean.afterPropertiesSet() 或任何声明的 init 方法)被调用之前,以及在任何 bean 初始化回调之后,从容器获得回调。
后处理器可以对 bean 实例采取任何操作,包括完全忽略回调。
Bean 后处理器通常检查回调接口,或者它可以用代理包装 bean。
一些 Infra AOP 基础设施类被实现为 bean 后处理器,以便提供代理包装逻辑。
ApplicationContext 会自动检测在配置元数据中定义且实现了 BeanPostProcessor 接口的任何 bean。
ApplicationContext 将这些 bean 注册为后处理器,以便稍后在 bean 创建时调用它们。
Bean 后处理器可以像任何其他 bean 一样部署在容器中。
请注意,当在配置类上使用 @Bean 工厂方法声明 BeanPostProcessor 时,工厂方法的返回类型应该是实现类本身或至少是 infra.beans.factory.config.BeanPostProcessor 接口,清楚地表明该 bean 的后处理器性质。
否则,ApplicationContext 无法在完全创建它之前按类型自动检测它。
由于 BeanPostProcessor 需要尽早实例化以便应用于上下文中其他 bean 的初始化,因此这种早期类型检测至关重要。
|
以编程方式注册
虽然推荐的 BeanPostProcessor 实例BeanPostProcessor 注册方法是通过 ApplicationContext 自动检测(如前所述),但您可以使用 addBeanPostProcessor 方法以编程方式针对 ConfigurableBeanFactory 注册它们。
当您需要在注册之前评估条件逻辑,甚至在层次结构中的上下文之间复制 bean 后处理器时,这可能很有用。
但是请注意,以编程方式添加的 BeanPostProcessor 实例不遵守 Ordered 接口。
在这里,注册的顺序决定了执行的顺序。
还要注意,无论是否有任何显式排序,以编程方式注册的 BeanPostProcessor 实例总是在通过自动检测注册的实例之前进行处理。
|
BeanPostProcessor 实例和 AOP 自动代理实现 对于任何此类 bean,您应该会看到一条信息性日志消息: 如果您使用自动装配或 |
以下示例显示了如何在 ApplicationContext 中编写、注册和使用 BeanPostProcessor 实例。
示例:Hello World,BeanPostProcessor 风格
第一个示例说明了基本用法。
该示例显示了一个自定义 BeanPostProcessor 实现,该实现调用容器创建的每个 bean 的 toString() 方法,并将结果字符串打印到系统控制台。
以下清单显示了自定义 BeanPostProcessor 实现类定义:
-
Java
package scripting;
import infra.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// 简单地按原样返回实例化的 bean
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // 我们可以在这里返回任何对象引用...
}
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:infra/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。Infra 动态语言支持在名为 动态语言支持 的章节中有详细说明。)
以下 Java 应用程序运行前面的代码和配置:
-
Java
import infra.context.ApplicationContext;
import infra.context.support.ClassPathXmlApplicationContext;
import infra.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 : infra.scripting.groovy.GroovyMessenger@272961 infra.scripting.groovy.GroovyMessenger@272961
使用 BeanFactoryPostProcessor 自定义配置元数据
我们查看的下一个扩展点是 infra.beans.factory.config.BeanFactoryPostProcessor。
该接口的语义与 BeanPostProcessor 的语义相似,但有一个主要区别:BeanFactoryPostProcessor 对 bean 配置元数据进行操作。
也就是说,Infra IoC 容器允许 BeanFactoryPostProcessor 读取配置元数据,并可能在容器实例化除 BeanFactoryPostProcessor 实例之外的任何 bean 之前 更改它。
您可以配置多个 BeanFactoryPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanFactoryPostProcessor 实例的运行顺序。
但是,只有当 BeanFactoryPostProcessor 实现了 Ordered 接口时,您才能设置此属性。
如果您编写自己的 BeanFactoryPostProcessor,您也应该考虑实现 Ordered 接口。
有关更多详细信息,请参阅 BeanFactoryPostProcessor
和 Ordered 接口的 javadoc。
|
如果您想更改实际的 bean 实例(即从配置元数据创建的对象),那么您应该使用 此外, |
当 bean 工厂后处理器在 ApplicationContext 内部声明时,它会自动运行,以便将更改应用于定义容器的配置元数据。
Infra 包括许多预定义的 bean 工厂后处理器,例如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。
您还可以使用自定义 BeanFactoryPostProcessor —— 例如,注册自定义属性编辑器。
ApplicationContext 会自动检测部署在其中的任何实现了 BeanFactoryPostProcessor 接口的 bean。
它会在适当的时候将这些 bean 用作 bean 工厂后处理器。
您可以像部署任何其他 bean 一样部署这些后处理器 bean。
与 BeanPostProcessor 一样,您通常不希望将 BeanFactoryPostProcessor 配置为延迟初始化。
如果没有其他 bean 引用 Bean(Factory)PostProcessor,该后处理器根本不会被实例化。
因此,将其标记为延迟初始化将被忽略,并且 Bean(Factory)PostProcessor 将被急切地实例化,即使您在 <beans /> 元素的声明中将 default-lazy-init 属性设置为 true。
|
示例:类名替换 PropertySourcesPlaceholderConfigurer
您可以使用 PropertySourcesPlaceholderConfigurer 通过使用标准 Java Properties 格式在单独的文件中外部化 bean 定义中的属性值。
这样做使得部署应用程序的人员可以自定义特定于环境的属性,例如数据库 URL 和密码,而无需承担修改容器的主 XML 定义文件的复杂性或风险。
考虑以下基于 XML 的配置元数据片段,其中定义了一个具有占位符值的 DataSource:
<bean class="infra.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 定义的大多数属性和属性中的占位符。
此外,您可以自定义占位符的前缀和后缀。
使用 Infra 2.5 中引入的 context 命名空间,您可以使用专用的配置元素配置属性占位符。
您可以在 location 属性中提供一个或多个位置作为逗号分隔的列表,如下例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer 不仅在您指定的 Properties 文件中查找属性。
默认情况下,如果它在指定的属性文件中找不到属性,它会检查 Infra Environment 属性和常规 Java System 属性。
|
对于给定的应用程序,应该只定义一个这样的元素,其中包含它所需的属性。
只要它们具有不同的占位符语法( 如果您需要模块化用于替换的属性源,您不应该创建多个属性占位符。
相反,您应该创建自己的 |
|
您可以使用
如果类在运行时无法解析为有效类,则在即将创建 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 且具有 driver 和 url 属性的 bean 的容器定义一起使用。
只要路径的每个组件(除了被覆盖的最终属性外)都已经非空(大概由构造函数初始化),就支持复合属性名称。
在以下示例中,tom bean 的 fred 属性的 bob 属性的 sammy 属性设置为标量值 123:
tom.fred.bob.sammy=123
| 指定的覆盖值始终是文字值。它们不会被转换为 bean 引用。 当 XML bean 定义中的原始值指定 bean 引用时,此约定也适用。 |
使用 Infra 2.5 中引入的 context 命名空间,可以使用专用的配置元素配置属性覆盖,如下例所示:
<context:property-override location="classpath:override.properties"/>
使用 FactoryBean 自定义实例化逻辑
您可以为本身就是工厂的对象实现 infra.beans.factory.FactoryBean 接口。
FactoryBean 接口是 Infra IoC 容器实例化逻辑的一个可插拔点。
如果您有复杂的初始化代码,用 Java 表达比用(可能)冗长的 XML 表达更好,您可以创建自己的 FactoryBean,在该类中编写复杂的初始化,然后将您的自定义 FactoryBean 插入到容器中。
FactoryBean<T> 接口提供了三个方法:
-
T getObject(): 返回此工厂创建的对象的实例。该实例可能被共享,取决于此工厂是返回单例还是原型。 -
boolean isSingleton(): 如果此FactoryBean返回单例,则返回true,否则返回false。此方法的默认实现返回true。 -
Class<?> getObjectType(): 返回getObject()方法返回的对象类型,如果类型事先未知,则返回null。
FactoryBean 概念和接口在 Infra Framework 中的许多地方都有使用。
Infra 本身附带了 50 多个 FactoryBean 接口的实现。
当您需要向容器请求实际的 FactoryBean 实例本身而不是它生成的 bean 时,在调用 ApplicationContext 的 getBean() 方法时,在 bean 的 id 前加上 & 符号。
因此,对于给定的 id 为 myBean 的 FactoryBean,在容器上调用 getBean("myBean") 返回 FactoryBean 的产品,而调用 getBean("&myBean") 返回 FactoryBean 实例本身。