1.8. Container Extension Points
通常来讲,程序员不需要继承ApplicationContext去定制化Container的功能。Spring通过提供接口的方式给程序员,让程序员实现这些接口来定制化Container的功能,这些接口实现可以插入(plug)到Container中,Container以回调方式调用这些自定义接口,来改变或者增强Container的行为。继承接口比较轻量级,也可以更快速的定制相关功能。本节针对如何扩展Spring容器功能,介绍一些Spring提供的接口。
1.8.1. Customizing Beans by Using a BeanPostProcessor
可以通过继承BeanPostProcessor实现自定义BeanPostProcessor,并将实现插入到Spring container中,来实现对bean的自定义初始化逻辑、自定义解析等等。
示例代码:
package examples;
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;
}
}
Spring container支持一个或多个自定义BeanPostProcessor,假设自定义了多个BeanPostProcessor,那就存在哪个BeanPostProcessor先调用的问题,如果有调用顺序要求,那么需要再继承Ordered接口,优先级仍然是数字越小优先级越高,最高优先级和最低优先级分别定义为int类型的最小值和最大值:
public interface Ordered {
int HIGHEST_PRECEDENCE = -2147483648;
int LOWEST_PRECEDENCE = 2147483647;
int getOrder();
}
BeanPostProcessor回调方法的调用时机
顾名思义,分别为bean初始化前和初始化后调用的方法:
- postProcessBeforeInitialization
- postProcessAfterInitialization
BeanPostProcessor,从名称看,主要就是处理bean的,Spring中典型的一种BeanPostProcessor应用场景就是Spring AOP,通过提供专门将bean包装成proxy的BeanPostProcessor,Spring就实现了AOP功能。由于BeanPostProcessor是用来处理Container中其他bean的,所以Spring Container一定会优先实例化BeanPostProcessor类型的bean,那么这些BeanPostProcessor才能在之后处理普通的bean。
继承BeanPostProcessor是用来扩展Spring IoC container能力的常用方式之一:
Using callback interfaces or annotations in conjunction with a custom BeanPostProcessor implementation is a common means of extending the Spring IoC container
1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor
BeanFactoryPostProcessor和BeanPostProcessor命名有点相似,但是作用不同,BeanPostProcessor针对bean的实例做操作,而BeanFactoryPostProcessor是针对bean的定义做操作,很明显,BeanFactoryPostProcessor是在BeanPostProcessor操作之前的,因为此时还未对bean进行实例化。
同样,Spring也支持注入多个BeanFactoryPostProcessor,多个BeanFactoryPostProcessor运行的优先级也由Ordered接口控制。
BeanFactoryPostProcessor的主要作用:
A bean factory post-processor is automatically executed when it is declared inside an ApplicationContext, in order to apply changes to the configuration metadata that define the container.
Spring本身有多达50多种内置的BeanFactoryPostProcessor。比如PropertyOverrideConfigurer and PropertyPlaceholderConfigurer。
Example: The Class Name Substitution PropertyPlaceholderConfigurer
PropertyPlaceholderConfigurer可以通过读取外部配置数据,动态替换bean定义中的属性:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!--bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:jdbc.properties"/>
</bean-->
<context:property-placeholder location="classpath:jdbc.properties" system-properties-mode="ENVIRONMENT"/>
<bean id="dataSource" destroy-method="close"
class="examples.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>
</beans>
jdbc.properties:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
指定class名称PropertyPlaceholderConfigurer初始化bean的配置方法是老版本的配置方式(上述配置中被注掉的bean定义),新的Spring配置方式使用context:property-placeholder指定。通过指定jdbc.properties,继而读取其中配置,然后将bean定义中的以$符号开头的属性值全部替换成具体的值。
BasicDataSource:
package examples;
import lombok.Data;
@Data
public class BasicDataSource {
private String driverClassName;
private String url;
private String username;
private String password;
public void close() {
}
@Override
public String toString() {
return String.format("%s=[%s,%s,%s,%s].", BasicDataSource.class, driverClassName, url, username, password);
}
}
执行如下代码:
BasicDataSource dataSource = context.getBean("dataSource", BasicDataSource.class);
System.out.println(dataSource);
执行结果:
class examples.BasicDataSource=[org.hsqldb.jdbcDriver,jdbc:hsqldb:hsql://production:9002,sa,root].
通过执行结果可见,bean实例化后,具体属性就是jdbc.properties中配置的值。
context:property-placeholder支持配置systemPropertiesMode的值和代表的功能:
- "ENVIRONMENT" indicates placeholders should be resolved against the current Environment and against any local properties;
- "NEVER" indicates placeholders should be resolved only against local properties and never against system properties;
- "FALLBACK" indicates placeholders should be resolved against any local properties and then against system properties;
- "OVERRIDE" indicates placeholders should be resolved first against system properties and then against any local properties;
ENVIRONMENT是现版本默认值,其他三种都是为了前向兼容老版本的,这块一般用默认值即可,所以大多数时候不需要设置。
PropertyPlaceholderConfigurer甚至可以支持启动时再指定执行的类:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.something.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
PropertyPlaceholderConfigurer基本上可以用来替换bean定义所有支持的属性,具体大家根据源码可以深入研究。
Example: The PropertyOverrideConfigurer
PropertyOverrideConfigurer也是BeanFactoryPostProcessor的实现,它用来覆盖bean的成员变量。下面给出具体示例。
xml配置方式如下:
<context:property-override location="classpath:override.properties"/>
<bean id="overrideBean" class="examples.OverrideBean" autowire="byName"/>
<bean id="dependencyBean" class="examples.DependencyBean" autowire-candidate="true"/>
OverrideBean实现:
package examples;
import lombok.Data;
@Data
public class OverrideBean {
private String name;
private DependencyBean dependencyBean;
}
DependencyBean实现:
package examples;
import lombok.Data;
@Data
public class DependencyBean {
private String name;
}
override.properties:
overrideBean.name="override"
dependencyBean.name="dependency"
上述OverrideBean实例化之后,name的值是override,DependencyBean实例化过后,name的值是dependency。可见配置文件中的值已成功覆盖了bean原有的成员变量内容。override.properties配置文件中的配置方式为:
beanName.property
1.8.3. Customizing Instantiation Logic with a FactoryBean
官方同样提供了用户自定义FactoryBean来定制实例化逻辑,不过我估计这节是很多年以前写的(本节描述FactoryBean只有三个接口,实际上远远不止),现在FactoryBean的接口已经非常复杂,使用官方的默认实现已经够用。这节本身也很短,没有什么干货,就此略过。如果需要自己实现FactoryBean的可以自行翻看源码实现。
总结:
本节讲述了如何通过实现Spring提供的接口(BeanPostProcessor、BeanFactoryPostProcessor)来改变或者增强Spring Container的功能。这节比较偏Spring源码实现(讲述到的几个类实现实际上都是以接口形式注入到Spring Container中来改变或者增强功能)。比如PropertyPlaceholderConfigurer,建议通过分析源码看看Spring到底是如何实现相应功能的。
网友评论