我们看的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor
。这个接口的语义与 BeanPostProcessor 的语义相似,但有一个主要区别。BeanFactoryPostProcessor 对 Bean 配置元数据进行操作。也就是说,Spring IoC 容器让 BeanFactoryPostProcessor 读取配置元数据,并在容器实例化 BeanFactoryPostProcessor 实例之外的任何 bean 之前对其进行潜在的修改。
你可以配置多个 BeanFactoryPostProcessor 实例,你可以通过设置 order 属性控制这些BeanFactoryPostProcessor 实例的运行顺序。然而,只有当 BeanFactoryPostProcessor 实现了 Ordered 接口时,你才能设置这个属性。如果你编写自己的 BeanFactoryPostProcessor,你也应该考虑实现 Ordered 接口。更多细节请参见 BeanFactoryPostProcessor 和 Ordered 接口的 javadoc。
:::tips 需要注意的是:
如果你想改变实际的 Bean 实例(即从配置元数据中创建的对象),那么你需要使用 BeanPostProcessor(在前面的使用 BeanPostProcessor 定制 Bean 中描述)。虽然在技术上可以在 BeanFactoryPostProcessor 中处理 Bean 实例(例如,通过使用 BeanFactory.getBean()
),但这样做会导致过早的 Bean 实例化,违反了标准容器的生命周期。这可能会导致负面的副作用,比如绕过 Bean 的后置处理。
另外,BeanFactoryPostProcessor 实例是按容器范围的。这只在你使用容器层次结构时才有意义。如果你在一个容器中定义了一个 BeanFactoryPostProcessor,它将只应用于该容器中的 Bean 定义。一个容器中的 Bean 定义不会被另一个容器中的 BeanFactoryPostProcessor 实例进行后处理,即使两个容器都是同一层次结构的一部分。 :::
当 Bean 工厂在 ApplicationContext 内声明时,会自动运行 Bean 工厂后置处理器,以便对定义容器的配置元数据进行修改。Spring 包括一些预定义的 Bean Factory 后置处理器,如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。你也可以使用一个自定义的 BeanFactoryPostProcessor — 例如,注册自定义的属性编辑器。
ApplicationContext 会自动检测被部署到其中的实现了 BeanFactoryPostProcessor 接口的任何 Bean。它在适当的时候将这些 Bean 用作 Bean Factory 后处理器。你可以像部署其他 Bean 一样部署这些后处理器 Bean。
:::tips
与 BeanPostProcessors 一样,你通常不希望将 BeanFactoryPostProcessors 配置为延迟初始化。如果没有其他 bean 引用 Bean(Factory)PostProcessor,该 PostProcessor 将根本不会被实例化。因此,将其标记为延迟初始化将被忽略,即使你在
例子:类名替换 PropertySourcesPlaceholderConfigurer
你可以使用 PropertySourcesPlaceholderConfigurer,通过使用标准的 Java 属性格式将属性值从 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" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<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>
这个例子显示了从外部属性文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer 被应用到元数据中,取代了 DataSource 的一些属性。要替换的值被指定为 ${property-name}
形式的占位符,它遵循 Ant 和 log4j 以及 JSP EL 的风格。
com/something/jdbc.properties 内容如下
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,在运行时将 ${ jdbc.username }
字符串替换为值 sa
,这同样适用于与属性文件中的键匹配的其他占位符值。
使用 Spring 2.5 中引入的 context 命名空间,您可以使用专用的配置元素配置属性占位符。可以在 location 属性中以逗号分隔的列表形式提供一个或多个位置,如下面的示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer 不仅会在你指定的属性文件中寻找属性。默认情况下,如果它不能在指定的属性文件中找到一个属性,它会检查 Spring 环境属性和常规 Java 系统属性。
你可以使用 PropertySourcesPlaceholderConfigurer 来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子说明了如何这样做:
<!-- 这个例子 class 正确的应该是 org.springframework.context.support.PropertySourcesPlaceholderConfigurer
不知道是不是官方文档的 bug,写错了类路径
-->
<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 时,也就是在非 lazy-init Bean 的ApplicationContext 的 preInstantiateSingletons()
阶段,Bean 的解析会失败。
例子:属性重写 PropertyOverrideConfigurer
PropertyOverrideConfigurer(org.springframework.beans.factory.config.PropertyOverrideConfigurer
) 是另一个 Bean 工厂的后处理器,与 PropertySourcesPlaceholderConfigurer 相似,但与后者不同的是,原始定义可以为 Bean 属性设置默认值或根本没有值。如果覆盖的属性文件中没有某个 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
:::tips 指定的覆盖值总是字面值。它们不被翻译成 bean 引用。当 XML Bean 定义中的原始值指定了一个 bean 引用时,这一约定也适用。 :::
通过 Spring 2.5 中引入的上下文命名空间,可以用一个专门的配置元素来配置属性重写,如下例所示。
<context:property-override location="classpath:override.properties"/>
简单说:可以实现外部的配置文件中的属性,覆盖掉预先配置好的,比如下面这个例子
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
>
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations">
<value>classpath:jdbc.properties</value>
</property>
</bean>
<bean id="jdbc" class="cn.mrcode.study.springdocsread.web.Hello">
<property name="url" value="123"></property>
</bean>
</beans>
package cn.mrcode.study.springdocsread.web;
/**
* @author mrcode
*/
public class Hello {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://production:9002
启动后,会将名为 jdbc 的 Hello 对象实例中的 url 属性变成文件中配置的值,这就是覆盖 :::tips 需要注意的是:文件中配置的属性或则 bean 名称一定要存在,否则就会报错 :::