# 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实例本身。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/1.the-ioc-container/1.8container-extension-points.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
