# 1.8 容器扩展点

通常，应用程序开发人员不需要子类化ApplicationContext实现类。相反，可以通过插入特殊集成接口的实现来扩展Spring IOC容器。接下来的几节将描述这些集成接口。

## 1.8.1 使用BeanPostProcessor自定义Beans

BeanPostProcessor接口定义了回调方法，你可以实现这些方法来提供自己的（或覆盖容器的默认）实例化逻辑、依赖解析逻辑等。如果希望在Spring容器完成对bean的实例化、配置和初始化之后实现一些自定义逻辑，可以插入一个或多个自定义BeanPostProcessor实现。

你可以配置多个BeanPostProcessor实例，并且可以通过设置Order属性来控制这些BeanPostProcessor实例的执行顺序。只有在BeanPostProcessor实现Ordered接口时，才能设置此属性。如果你编写自己的BeanPostProcessor，那么也应该考虑实现Ordered的接口。有关进一步的详细信息，请参阅BeanPostProcessor和有序接口的JavaDoc。另请参见BeanPostProcessor实例的编程注册说明。

> BeanPostProcessor实例对bean（或对象）实例进行操作。也就是说，Spring IOC容器实例化一个bean实例，然后beanPostProcessor实例再继续完成它们的工作。
>
> BeanPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果你在一个容器中定义了beanPostProcessor，那么它只post-processes该容器中的bean。换句话说，在一个容器中定义的bean不会由在另一个容器中定义的beanPostProcessor进行后处理，即使这两个容器是同一层次结构的一部分。
>
> 要更改实际的bean定义（即定义bean的蓝图），你需要使用beanFactoryPostProcessor。

org.springframework.beans.factory.config.BeanPostProcessor接口正好由两个回调方法组成。当此类注册为容器的post-processor时，对于容器创建的每个bean实例，post-processor在调用容器初始化方法（如InitializingBean.afterPropertiesSet（）或任何声明的init方法）之前，以及在任何bean初始化之后，都会从容器中调用回调。post-processor可以对bean实例执行任何操作，包括完全忽略回调。bean post-processor通常检查回调接口，或者它可以用代理包装bean。为了提供代理包装逻辑，一些Spring AOP基础结构类被实现为bean post-processors。

ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理器，以便在bean创建之后可以调用它们。bean post-processors 可以与任何其他bean相同的方式部署在容器中。

注意，当通过在configuration类上使用@Bean factory方法声明BeanPostProcessor时，factory方法的返回类型应该是实现类本身，或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口，并清楚地表面该bean是post-processor。否则，ApplicationContext无法在完全创建它之前按类型自动检测到它。由于需要尽早实例化BeanPostProcessor以应用于上下文中其他bean的初始化，因此这种早期类型检测非常关键。

> 以编程方式注册BeanPostProcessor实例
>
> 虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测（如前所述），但你可以使用addBeanPostProcessor方法在ConfigurableBeanFactory中以编程方式注册它们。 当你需要在注册之前评估条件逻辑，或者甚至需要跨层次结构中的上下文复制bean post processors时，这可能非常有用。但是，请注意，以编程方式添加的BeanPostProcessor实例并不尊重有序的接口。在这里，登记的顺序决定执行的顺序。还要注意，以编程方式注册的BeanPostProcessor实例总是在注册为自动检测的实例之前进行处理，而不接收任何显式排序。
>
> BeanPostProcessor实例和AOP自动代理
>
> 实现BeanPostProcessor接口的类是特殊的，由容器进行不同的处理。作为ApplicationContext特殊启动阶段的一部分，所有BeanPostProcessor实例和这些实例直接引用的bean都在启动时实例化。
>
> 接下来，所有beanPostProcessor实例都以排序的方式注册，并应用于容器中所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的，所以BeanPostProcessor实例和它们直接引用的bean都不符合自动代理的条件，因此它们中没有aspects woven。
>
> 对于任何这样的bean，你都应该看到一条日志消息：bean somebean不适合被所有的BeanPostProcessor接口处理（例如：不适合自动代理）。
>
> 如果你使用autowiring或@Resource将bean连接到BeanPostProcessor，那么在搜索类型匹配的依赖项候选项时，Spring可能会访问意外的bean，因此，它们不适合自动代理或选用其他类型的post-processing方式。例如，如果你有一个用@Resource注解的依赖项，其中字段或setter名称与bean的声明名称不直接匹配，并且没有使用name属性，那么spring将通过类型来访问其他bean。

下面的例子展示了怎么在ApplicationContext中写，注册，和使用BeanPostProcessor。

**例子Hello World, BeanPostProcessor-style**

第一个例子是基本应用，这个例子展示了自定义的BeanPostProcessor实现，调用toString()方法并打印到系统输出。

下面是自定义BeanPostProcessor：

```
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;
    }
}
```

下面的bean使用了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动态语言支持在标题为“动态语言支持”的章节中详细介绍。）

下面是调用的java应用程序：

```
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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}
```

程序的输出如下：

```
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
```

**例子：RequiredAnnotationBeanPostProcessor**

将回调接口或注解与自定义BeanPostProcessor实现结合使用是扩展Spring IOC容器的常用方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor-一个随Spring发行版一起提供的BeanPostProcessor实现，它要求用（任意）注解标记的bean上的JavaBean属性都需要一个真实值。

## 1.8.2 使用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实例（即从配置元数据创建的对象），则需要使用BeanPostProcessor（如前面使用BeanPostProcessor自定义bean中所述）。虽然在技术上可以在BeanFactoryPostProcessor中使用bean实例（例如，通过使用BeanFactory.getBean（）），但这样做会导致过早的bean实例化，违反标准容器生命周期。这可能会导致负面影响，例如绕过bean post处理。

此外，BeanFactoryPostProcessor实例的作用域是每个容器。这仅在使用容器层次结构时才相关。如果在一个容器中定义BeanFactoryPostProcessor，那么它只应用于该容器中的bean定义。一个容器中的bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理，即使这两个容器属于同一层次结构。

bean工厂后处理器在ApplicationContext中声明时自动执行，以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理器，例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。你还可以使用自定义BeanFactoryPostProcessor-来注册自定义属性编辑器。

ApplicationContext会自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候将这些bean用作bean工厂post-processors。你可以像部署任何其他bean一样部署这些后处理器bean。

> 与BeanPostProcessors一样，你通常不希望为延迟初始化配置BeanFactoryPostProcessors。如果没有其他bean引用bean（工厂）后处理器，则该后处理器将不会被实例化。因此，将其标记为lazy初始化将被忽略，并且即使在你的元素的声明中将default-lazy-init属性设置为true，Bean(Factory)PostProcessor也将被优先实例化。

**PropertyPlaceholderConfigurer类名替换**

通过使用标准Java Properties格式，可以使用PropertyPlaceholderConfigurer从一个单独的文件中的bean定义外部化属性值。这样做可以使部署应用程序的人员自定义特定于环境的属性，如数据库URL和密码，而不必为容器修改一个或多个XML定义主文件从而增加复杂性或风险。

考虑以下基于XML的配置元数据片段，其中定义了具有占位符值的数据源：

```
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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>
```

这个例子展示了属性被配置在外部的Properties文件中。在运行时，使用PropertyPlaceholderConfigurer将元数据替换成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'，其他的占位符也会被替换成相应的值。PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外，还可以自定义占位符前缀和后缀。

使用Spring2.5引入的context命名空间，可以使用专用配置元素配置属性占位符。可以在“位置”属性中以逗号分隔的列表形式提供一个或多个位置，如下示例所示：

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

PropertyPlaceholderConfigurer不仅在指定的属性文件中查找属性。默认情况下，如果它在指定的属性文件中找不到属性，它也会检查Java系统属性。可以通过使用以下三个支持的整数值之一设置配置器的systemPropertiesMode属性来自定义此行为：

* never (0): 从不检查系统属性。
* fallback (1): 如果属性在声明的文件里面找不到，则去检查系统属性。这个是默认的模式。
* override (2): 先检查系统属性，这会让系统属性覆盖其他的来源。

> 你可以使用PropertyPlaceholderConfigurer来替换类的名称，这个在运行时需要类的特殊实现时很有用，如下例子所示：

```
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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时，bean的解析将失败，这是在非lazy init bean的ApplicationContext的preInstantiateSingletons（）阶段进行的。

**例子PropertyOverrideConfigurer**

PropertyOverrideConfigurer是另外一个post-processor bean工程，和PropertyPlaceholderConfigurer相似，但是和它不同的是，对bean的定义来说原始定义可以有默认值或者没有任何值。 如果overriding Properties没有bean某个特定属性，那么会使用默认的上下文定义。

注意，bean定义不知道被覆盖，所以从XML定义文件中看，并不知道使用的是 override configurer。如果多个PropertyOverrideConfigurer实例为同一个bean属性定义不同的值，则最后一个实例将因覆盖机制而获胜。

属性文件配置行如下格式：

```
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引用时，此约定也适用。

使用Spring2.5引入的context命名空间，可以使用专用配置元素配置属性占位符。可以在“位置”属性中以逗号分隔的列表形式提供一个或多个位置，如下示例所示：

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

## 1.8.3 使用FactoryBean自定义实例化逻辑

你可以为本身是工厂的对象实现org.springframework.beans.factory.factorybean接口。

FactoryBean接口是Spring IOC容器实例化逻辑的一个可插入点。如果需要在java中实现初始化代码，而不是一个（潜在的）冗长的XML中，那么你可以创建自己的FactoryBean，在该类中编写复杂的初始化，然后将自定义的FactoryBean插入容器中。

FactoryBean接口提供3个方法：

* Object getObject(): 返回工厂创建的实例，该实例可能是被共享的， 取决于该实例是单例还是多例模式。
* boolean isSingleton():判断FactoryBean返回的是单例还是多例。
* Class getObjectType():返回getObject() 方法返回的类型，如果提前不知道类型，那么返回null。

factrybean概念和接口在Spring框架中的许多地方使用。超过50个FactoryBean接口实现与Spring本身一起提供。

当需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时，在调用ApplicationContext的getbean（）方法时，在bean的ID前面加上符号（&）。因此，对于ID为myBean的给定FactoryBean，在容器上调用getBean（“myBean”）返回FactoryBean生成的bean，而调用getBean（“\&myBean”）则返回FactoryBean实例本身。
