目录
1、使用 BeanPostProcessor 自定义 bean
(1)示例:Hello World,BeanPostProcessor 样式
(2)示例:AutowiredAnnotationBeanPostProcessor
2、使用 BeanFactoryPostProcessor 定义配置元数据
(1)示例:类名替换 PropertySourcesPlaceholderConfigurer
(2)示例:PropertyOverrideConfigurer
一般的,开发者不需要采用实现 ApplicationContext 接口的方式来实现 Spring 容器的拓展,他们可以实现特殊的集成接口来实现此目的。
1、使用 BeanPostProcessor 自定义 beans
BeanPostProcessor 接口定义了两个回调方法,支持在 bean 初始化前后进行回调,在回调方法中,支持用户对 bean 的实例化逻辑和依赖项解析逻辑等进行自定义。另外,你可以添加一个或多个 BeanPostProcessor 接口的实现来自定义 bean 的初始化逻辑。// 后置处理器,是在 Bean 被实例化、属性赋值后,实现一些自定义逻辑。该接口是在已经实例化的 bean 上进行操作。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
如果配置了多个 BeanPostProcessor 实例,那么在这些实例实现了 Ordered 接口后,可以通过在 BeanPostProcessor 实例中添加 order 属性来控制这些 BeanPostProcessor 实例的运行顺序,比如 AutowiredAnnotationBeanPostProcessor ,就实现了 Ordered 接口。所以当你自定义自己的 BeanPostProcessor 实例时,也应该考虑这个后置处理器的运行顺序。// 自定义的后置处理器应该考虑它们的运行顺序,因为 Spring 也自定义了很多后置处理器。
注意,BeanPostProcessor 是对 bean 的实例进行操作。也就是说,只有当 Spring 容器实例化一个 bean 实例后,BeanPostProcessor 实例的逻辑才会开始执行。
BeanPostProcessor 实例的作用域是每一个容器。这只与按层次结构使用容器的有关。在当前容器中定义的 BeanPostProcessor 实例,它只会对该容器中的 bean 进行后置处理。它不会对另一个容器中的 beans 进行任何操作,即使这两个容器都是相同层次结构的一部分。// 按层次结构使用容器可以理解为 Controller 层、Service 层、DAO 层...
BeanPostProcessor 接口通常用来检测回调接口,或者对 bean 进行代理包装。一些 Spring AOP 的基础类就是基于 BeanPostProcessor 接口来实现的,通过这种方式为 AOP 提供代理的包装的逻辑。// AOP 的代理基于 bean 的后置处理器来实现
ApplicationContext 容器会自动检测定义在配置文件中并实现了 BeanPostProcessor 接口的 beans,然后将这些 beans 注册为 BeanPostProcessor 的实例,等到容器在创建 bean 的时候,再调用这些后置处理器的回调方法。BeanPostProcessor 的实例跟 Spring 容器中的其他 beans 一样,都可以采用相同的方式部署在容器当中。
需要注意的是,当在配置类中使用 @Bean 注解标识的工厂方法去声明一个 BeanPostProcessor 实例时,那么该工厂方法返回的类型应该是该后置处理器本身(BeanPostProcessor 接口的实现类),或者至少是 BeanPostProcessor 接口。通过明确的返回类型,可以清楚的向 Spring 容器表明该 bean 是一个后置处理器。否则,Spring 容器在完全创建这个 bean 前并不能自动通过类型进行识别。BeanPostProcessor 类型的 beans 需要提前进行实例化,然后才能应用到其他 beans 的初始化当中,所以,Spring 早期的类型检测非常重要。// BeanPostProcessor 实例需要提前被实例化。
以编程方式注册 BeanPostProcessor 实例
虽然推荐使用Spring 容器自动检测来注册 BeanPostProcessor 实例,但你仍然可以通过实现 ConfigurableBeanFactory 接口中的 addBeanPostProcessor() 方法,通过编程方式向容器注册 Bean 后置处理器的实例。当你在后置处理器注册之前需要进行一些条件判断,或者需要跨容器复制 BeanPostProcessor 实例,那么这种方式将会很有用。不过,要注意的是,通过编程方式注册的 BeanPostProcessor 实例不会遵循 Ordered 接口定义的顺序。在这种方式下,后置处理器注册的顺序决定了执行的顺序。以编程方式注册的 BeanPostProcessor 实例总是在 Spring 容器自动注册的 BeanPostProcessor 实例之前被处理,所以,以编程方式注册的 BeanPostProcessor 实例不用考虑任何显式指定的顺序。// 可以手动注册后置处理器,以编程方式注册的 BeanPostProcessor 会优先于自动注册的BeanPostProcessor 执行
BeanPostProcessor 实例和 AOP 自动代理
实现 BeanPostProcessor 接口的类具有特殊性,会被 Spring 容器区别对待。因为 Spring AOP 自动代理本身是通过 BeanPostProcessor 实现的,因此无论是 BeanPostProcessor 实例还是它们直接引用的 bean 对象都不适合进行自动代理,所以,这些后置处理器的实例并没有嵌入切面。// BeanPostProcessor 实例不能进行 AOP 代理
如果你使用自动装配或 @Resource 注解将 bean 装配到 BeanPostProcessor 中,那么 Spring 在通过类型匹配依赖项时,可能访问到意料之外的 bean。所以,针对这些 bean,需要使他们不能被自动代理或者不能成为 BeanPostProcessor。// BeanPostProcessor 依赖的 bean 不能被自动代理。
下面的示例将演示如何在 ApplicationContext 中编写、注册和使用 BeanPostProcessor 实例。
(1)示例:Hello World,BeanPostProcessor 样式
第一个示例演示了基本用法。该示例展示了一个自定义 BeanPostProcessor 的实现,该实现在容器创建每个 bean 时调用其 toString() 方法,并将结果字符串打印到系统控制台。
下面的代码展示了自定义 BeanPostProcessor 的实现类定义:
package scripting;
import org.springframework.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:org/springframework/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 。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 = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
上述应用程序的输出如下所示:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
(2)示例:AutowiredAnnotationBeanPostProcessor
扩展 Spring 容器的常用方式,是将回调接口或注解与自定义的 BeanPostProcessor 实现结合使用。一个具体例子是 Spring 的 AutowiredAnnotationBeanPostProcessor —— 一个随 Spring 发行版一起发布的 BeanPostProcessor 实现,自动装配带注解的字段、setter 方法和任意配置的方法。// Spring 有很多的 BeanPostProcessor,通过阅读源码可以详细的了解每一个后置处理器的调用时机。注意,这里的注解不包括自动装配的注解?
2、使用 BeanFactoryPostProcessor 定义配置元数据
BeanFactoryPostProcessor 和 BeanPostProcessor 主要不同点在于,BeanFactoryPostProcessor 操作的是 bean 的元数据。也就是说,Spring 容器允许 BeanFactoryPostProcessor 读取配置元数据,并在容器实例化 BeanFactoryPostProcessor 实例以外的任何 bean 之前更改配置元数据。// Bean 工厂的后置处理器可以更改 bean 的配置元数据
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
你可以配置多个 BeanFactoryPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanFactoryPostProcessor 实例的运行顺序。但是,如果 BeanFactoryPostProcessor 实现了Ordered 接口,则只能设置此属性。如果编写自己的 BeanFactoryPostProcessor,也应该考虑实现 Ordered 接口。// 通过 Ordered 接口来实现实例的执行顺序。
如果你想要更改实际的bean实例,那么你需要使用BeanPostProcessor。虽然技术上可以在 BeanFactoryPostProcessor 中使用 bean 实例(例如,通过使用 BeanFactory.getBean() ),但这样做会导致 bean 过早的实例化,违反标准的容器生命周期。这可能会造成一些负面的影响,比如绕过 bean 的后置处理过程。// BeanFactory.getBean() 会导致 bean 过早的实例化。
BeanFactoryPostProcessor 的作用域是每一个容器,这只与使用容器的层次结构有关。如果你在一个容器中定义了 BeanFactoryPostProcessor,那么它只应用于该容器中的 bean 定义。一个容器中的 Bean 定义不会被另一个容器中的 BeanFactoryPostProcessor 实例进行后置处理,即使两个容器属于同一个层次结构的一部分。// BeanFactoryPostProcessor 的作用域,现实中两个容器的情况是什么样子的?
当在 ApplicationContext 中声明一个 BeanFactoryPostProcessor 后,processor 将自动运行,这主要为了方便对配置元数据进行更改。Spring 包括许多预定义的 bean 工厂后置处理器,比如 PropertyOverrideConfigurer 和PropertySourcesPlaceholderConfigurer 。您还可以使用自定义的 BeanFactoryPostProcessor —例如,注册自定义的属性编辑器。// bean 工厂后置处理器自动运行。
ApplicationContext 会自动检测实现了 BeanFactoryPostProcessor 接口的 beans。它会在适当的时候把这些 beans 当作 BeanFactoryPostProcessor。你可以像部署其他 bean 一样部署这些工厂后置处理器的 beans。// ApplicationContext 会自动检测 BeanFactoryPostProcessor 实例
与 BeanPostProcessors 一样,通常不会为 BeanFactoryPostProcessors 配置延迟初始化(懒加载)。因为如果没有其他 bean 引用 bean(工厂) 后置处理器,那么该后置处理器根本不会被实例化。因此,懒加载配置将会被忽略,即使在 <beans /> 标签中将 default-lazy-init 属性设置为 true,Bean(Factory)PostProcessor 也会提前实例化。// 后置处理器不能被设置为懒加载,即使设置了,设置也无效。
(1)示例:类名替换 PropertySourcesPlaceholderConfigurer
你可以使用 PropertySourcesPlaceholderConfigurer,通过使用标准 Java Properties 格式将 bean 定义中的属性值分离到单独的文件中。这样做可以使开发人员自定义特定环境的配置,比如数据库 url 和密码,从而减少需改 xml 配置文件的风险。// 支持 Java Properties 格式配置
考虑以下基于 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>
该示例展示了从外部 properties 文件中配置的属性。在运行时,PropertySourcesPlaceholderConfigurer 被应用到数据源某些属性的替换上。数据源属性要替换的值被指定为表单的 ${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 定义的大多数属性和属性中的占位符。此外,你还可以自定义占位符前缀和后缀。// Configurer 提供占位符的解析过程。
通过在 Spring 2.5 中引入的容器命名空间,你可以使用专用的配置元素配置 PropertySourcesPlaceholderConfigurer 。你可以在 location 属性中以逗号分隔的列表形式提供一个或多个位置,如下例所示:// 使用命名空间进行配置
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer 不仅查找你指定的 properties 文件中的属性,默认情况下,如果在指定的属性文件中找不到属性,它还会检查 Spring 环境的 properties 和常规的 Java 系统properties 。// Spring Environment properties and regular Java System properties.
你可以使用 PropertySourcesPlaceholderConfigurer 来替换类名,当你必须在运行时选择特定的实现类时,这有时很有用。下面的例子展示了如何做到这一点:
<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 时解析失败,此时处在非惰性初始化 bean 的 ApplicationContext 容器的 preInstantiateSingletons() 阶段。
(2)示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer 是另一个 bean 工厂后置处理器。在 bean 的原始定义中,bean 的属性值可以有默认值也可以没有,PropertyOverrideConfigurer 的作用就是支持对 bean 属性值的覆盖,当定义属性覆盖的 Properties 文件中没有匹配到 bean 的属性项,那么该属性项的值还是使用容器中定义的默认值。// 支持属性覆盖
需要注意的是,bean 定义本身并不知道正在被覆盖,所以不能立即从 XML 配置中看出正在使用覆盖配置器。如果多个 PropertyOverrideConfigurer 实例为同一个 bean 属性定义了不同的值,由于覆盖机制,最后使用最后一次被覆盖的值。// 如果存在多个覆盖,使用最后覆盖的值
Properties 文件配置行采用以下格式:
beanName.property=value
下边展示了该格式的一个示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
通过在 Spring 2.5 中引入的容器命名空间,可以使用专用的配置元素配置属性覆盖,如下例所示:
<context:property-override location="classpath:override.properties"/>
3、自定义实例化逻辑 FactoryBean
可以为本身就是工厂对象的 bean 实现 FactoryBean 接口。
FactoryBean 接口是 Spring 容器实例化中一个可以插入的点,你可以创建自己的 FactoryBean,在该类中编写复杂的初始化逻辑,然后将自定义的 FactoryBean 插入到容器中。// FactoryBean 是一个特殊的 bean
FactoryBean<T> 接口提供三个方法:
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
- T getObject():返回该工厂创建的实例。该实例是否可以被共享,取决于该实例是不是单例。
- boolean isSingleton():返回 FactoryBean 是不是单例的布尔值,默认为 true。
- Class<?> getObjectType():返回对象的类型,如果对象类型不能获取,返回 null。
FactoryBean 接口在 Spring 框架中的许多地方都有使用。Spring 本身附带了 FactoryBean 接口的 50 多个实现。
当你需要向容器请求 FactoryBean 的原实例,而不是它产生的 bean 时,在调用 ApplicationContext 的 getBean() 方法时,需将与符号 (&) 作为 bean 的 id 的前缀。所以,对于一个 id 为 myBean 的 FactoryBean ,调用 getBean("myBean") 将返回 FactoryBean 创建的 bean,调用 getBean("&myBean") 将返回 FactoryBean 的原实例(原实例由 Spring 容器创建)。