我们看的下一个扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义与 BeanPostProcessor 的语义相似,但有一个主要区别。BeanFactoryPostProcessor 对 Bean 配置元数据进行操作。也就是说,Spring IoC 容器让 BeanFactoryPostProcessor 读取配置元数据,并在容器实例化 BeanFactoryPostProcessor 实例之外的任何 bean 之前对其进行潜在的修改。

你可以配置多个 BeanFactoryPostProcessor 实例,你可以通过设置 order 属性控制这些BeanFactoryPostProcessor 实例的运行顺序。然而,只有当 BeanFactoryPostProcessor 实现了 Ordered 接口时,你才能设置这个属性。如果你编写自己的 BeanFactoryPostProcessor,你也应该考虑实现 Ordered 接口。更多细节请参见 BeanFactoryPostProcessorOrdered 接口的 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 将根本不会被实例化。因此,将其标记为延迟初始化将被忽略,即使你在 元素的声明中把 default-lazy-init 属性设置为 true,Bean(Factory)PostProcessor 也将被急切地实例化。 :::

例子:类名替换 PropertySourcesPlaceholderConfigurer

你可以使用 PropertySourcesPlaceholderConfigurer,通过使用标准的 Java 属性格式将属性值从 Bean 定义中外化到一个单独的文件中。这样做使部署应用程序的人能够定制特定环境的属性,如数据库 URL 和密码,而不需要修改容器的主要 XML 定义文件或文件的复杂性或风险。

考虑以下基于 xml 的配置元数据片段,其中定义了带有占位符值的 DataSource:

  1. <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
  2. <property name="locations" value="classpath:com/something/jdbc.properties"/>
  3. </bean>
  4. <bean id="dataSource" destroy-method="close"
  5. class="org.apache.commons.dbcp.BasicDataSource">
  6. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  7. <property name="url" value="${jdbc.url}"/>
  8. <property name="username" value="${jdbc.username}"/>
  9. <property name="password" value="${jdbc.password}"/>
  10. </bean>

这个例子显示了从外部属性文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer 被应用到元数据中,取代了 DataSource 的一些属性。要替换的值被指定为 ${property-name}形式的占位符,它遵循 Ant 和 log4j 以及 JSP EL 的风格。

com/something/jdbc.properties 内容如下

  1. jdbc.driverClassName=org.hsqldb.jdbcDriver
  2. jdbc.url=jdbc:hsqldb:hsql://production:9002
  3. jdbc.username=sa
  4. jdbc.password=root

因此,在运行时将 ${ jdbc.username }字符串替换为值 sa,这同样适用于与属性文件中的键匹配的其他占位符值。

使用 Spring 2.5 中引入的 context 命名空间,您可以使用专用的配置元素配置属性占位符。可以在 location 属性中以逗号分隔的列表形式提供一个或多个位置,如下面的示例所示:

  1. <context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer 不仅会在你指定的属性文件中寻找属性。默认情况下,如果它不能在指定的属性文件中找到一个属性,它会检查 Spring 环境属性和常规 Java 系统属性。

你可以使用 PropertySourcesPlaceholderConfigurer 来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子说明了如何这样做:

  1. <!-- 这个例子 class 正确的应该是 org.springframework.context.support.PropertySourcesPlaceholderConfigurer
  2. 不知道是不是官方文档的 bug,写错了类路径
  3. -->
  4. <bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
  5. <property name="locations">
  6. <value>classpath:com/something/strategy.properties</value>
  7. </property>
  8. <property name="properties">
  9. <value>custom.strategy.class=com.something.DefaultStrategy</value>
  10. </property>
  11. </bean>
  12. <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 属性定义了不同的值,由于覆盖机制的存在,最后一个实例获胜。

属性文件配置行采用以下格式:

  1. beanName.property=value

下面的清单显示了一个格式示例:

  1. dataSource.driverClassName=com.mysql.jdbc.Driver
  2. dataSource.url=jdbc:mysql:mydb

此示例文件可与容器定义一起使用,该容器定义包含一个名为 dataSource 的 bean,该 bean 具有 driverClassName 和 url 属性。

也支持复合属性名,只要路径中的每个组件,除了被重载的最终属性外,都已经是非空的(大概是被构造函数初始化了)。在下面的例子中,Tom Bean 的 fred 属性的 bob 属性的 sammy 属性被设置为 123。

  1. tom.fred.bob.sammy=123

:::tips 指定的覆盖值总是字面值。它们不被翻译成 bean 引用。当 XML Bean 定义中的原始值指定了一个 bean 引用时,这一约定也适用。 :::

通过 Spring 2.5 中引入的上下文命名空间,可以用一个专门的配置元素来配置属性重写,如下例所示。

  1. <context:property-override location="classpath:override.properties"/>

简单说:可以实现外部的配置文件中的属性,覆盖掉预先配置好的,比如下面这个例子

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
  7. >
  8. <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
  9. <property name="locations">
  10. <value>classpath:jdbc.properties</value>
  11. </property>
  12. </bean>
  13. <bean id="jdbc" class="cn.mrcode.study.springdocsread.web.Hello">
  14. <property name="url" value="123"></property>
  15. </bean>
  16. </beans>
  1. package cn.mrcode.study.springdocsread.web;
  2. /**
  3. * @author mrcode
  4. */
  5. public class Hello {
  6. private String url;
  7. public String getUrl() {
  8. return url;
  9. }
  10. public void setUrl(String url) {
  11. this.url = url;
  12. }
  13. }

jdbc.properties

  1. jdbc.url=jdbc:hsqldb:hsql://production:9002

启动后,会将名为 jdbc 的 Hello 对象实例中的 url 属性变成文件中配置的值,这就是覆盖 :::tips 需要注意的是:文件中配置的属性或则 bean 名称一定要存在,否则就会报错 :::