容器的扩展
通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能。但是Spring IoC容器确实可以通过实现接口来增加一些功能。下面将描述一下这些接口。
接下来主要分是哪个部分来讲解:
1.通过 BeanPostProcessor后置处理器来对一个bean(
可以是监控bean或者增加bean
)
2.通过BeanFactoryPostProcessor工厂后置bean处理器来定义配置元数据(容器实例初始话Bean之前就改变配置元数据
)
3.自定义FactoryBean的初始化逻辑
1.通过 BeanPostProcessor后置处理器来对一个bean
BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等
。如果开发者只是想在Spring容器完成了实例化,配置以及初始化Bean之后来做一些操作的话,可以通过使用BeanPostProcessor来做到
。
开发者可以配置多个BeanPostProcessor实例,开发者可以通过配置order属性来指定配置的BeanPostProcessor的执行顺序
。当然,想要配置顺序必须同时要实现Ordered接口
。如果开发者写了自己的BeanPostProcessor,开发者最好同时考虑实现Ordered接口
BeanPostProcessors是操作Bean实例的
,换言之,Spring IoC容器必须先初始化好Bean,然后BeanPostProcessors才开始工作
。
BeanPostProcessors作用范围是基于容器的
。当然,只有当开发者使用容器的层级的时候才是需要考虑的。如果开发者在容器中定义了一个BeanPostProcessor,这个实例只会在它所在的容器来处理Bean初始化以后的操作
。换言之,一个容器中的Bean不会被另一个容器中定义BeanPostProcessor的在初始化以后进行后续处理,甚至就算两个容器同属同一个容器的部分。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含了2个回调方法
。当这个接口的实例注册到容器当中时,对于每一个由容器创建的实例,这个后置处理器都会在容器开始进行初始化之前获得回调的调用。后置处理器可以针对Bean实例采取任何的操作,包括完全无视回调函数。Bean的后置处理器通常检查回调接口或者将Bean用代理包装一下
。一些诸如Spring AOP代理的基础类都是 通过Bean的后续处理器来实现的
。
ApplicationContext会自动检查配置的Bean是否有实现BeanPostProcessor接口,ApplicationContext会将这些Bean注册为后续处理器
,这样这些后续处理器就会在Bean创建之后调用
。Bean的后续处理器就像其他的Bean一样,由容器管理的
。
尽管Spring团队推荐的注册Bean的后续处理的方式是通过ApplicationContext的自动检查,但是
Spring也支持通过编程的方式,通过addBeanPostProcessor方法
。这种方式有的时候也很有用,当需要在注册前执行一些条件判断的时候,或者在结构化的上下文中复制Bean后续处理器的时候尤其有效。需要注意的是,通过编程实现的BeanPostProcessors是会忽略掉Ordered接口的
:由编程注册的BeanPostProcessors总是在自动检查到的BeanPostProcessors之前来执行的,而回忽略掉明确的顺序定义。
容器会特殊对待那些实现了BeanPostProcessor接口的类。所有的BeanPostProcessors和他们所引用的Bean都会在启动时直接初始化
,作为ApplicationContext启动的一个特殊阶段
。然后,所有的BeanPostProcessors会按顺序注册并应用到容器中的Bean
下面通过一个例子来演示一下
/**
* @Project: spring
* @description: 模拟一个普通的bean
* @author: sunkang
* @create: 2018-09-16 15:06
* @ModificationHistory who when What
**/
public class BeanDemo{
public BeanDemo() {
System.out.println("beanDemo已经初始化了");
}
}
/**
* 模拟 BeanPostProcessor来监控其他bean,进行相应的处理
*/
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor,Ordered {
//在bean初始化之后调用
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("beforeBean ''" + beanName + "'' created : " + bean.toString());
return bean;
}
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
System.out.println("afterBean ''" + beanName + "'' created : " + bean.toString());
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
在spring-extensionPoint.xml的配置中如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanPostProcessor" class="com.spring.extensionPoint.InstantiationTracingBeanPostProcessor" />
<bean class="com.spring.extensionPoint.BeanDemo"/>
</beans>
测试的演示:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
测试结果如下:可以发现BeanPostProcessor的两个回调方法在容器构造方法之后进行执行的
beanDemo已经初始化了
beforeBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
afterBean ''com.spring.extensionPoint.BeanDemo#0'' created : com.spring.extensionPoint.BeanDemo@1753acfe
spring的例子:
RequiredAnnotationBeanPostProcessor
使用回调接口或者注解和BeanPostProcessor实现是常见的来扩展Spring IoC容器的方式。Spring中的一个例子就是RequiredAnnotationBeanPostProcessor就是用来确保JavaBean的标记有注解的属性确实注入了依赖
。
2.通过BeanFactoryPostProcessor定义配置元数据
org.springframework.beans.factory.config.BeanFactoryPostProcessor
。语义上来说,这个接口有些类似BeanPostProcessor
,只是有一个很大的区别:BeanFactoryPostProcessor操作Bean配置元数据,也就是说,Spring允许BeanFactoryPostProcessor来读取配置源数据,并且可能会在容器实例初始话Bean之前就改变配置元数据
。
如前文所述,开发者可以配置多个BeanFactoryPostProcessors,而且开发者可以控制其具体执行的顺序。当然,配置顺序是必须实现Ordered接口的。如果实现了自己的BeanFactoryPostProcessor,开发者也应该考虑实现Ordered接口
在ApplicationContext中声明了后续处理器,Bean的后续处理器就会自动的执行,来实现在配置中定义的行为。Spring包括一些预定义好的后续处理器都可以使用,比如PropertyOverrideConfigurer
和PropertyPlaceholderCOnfigurer
,也能够使用自定义的BeanFactoryPostProcessor
,比如,来注册自定义属性编辑器
。
例子,类名替换BeanFactoryPostProcessor
下面就开始演示一下
/**
* @Project: spring
* @description: 模拟PropertyPlaceholderConfigurer来进行测试
* @author: sunkang
* @create: 2018-09-16 16:15
* @ModificationHistory who when What
**/
public class DataSourceConfig {
private String driverClassName;
private String url;
private String username;
private String password;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "DataSourceConfig{" +
"driverClassName='" + driverClassName + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
在extensionPoint目录下放置datasouce.properties文件,文件内容如下
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
在spring-extensionPoint.xml配置如下
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:extensionPoint/datasouce.properties"/>
</bean>
<bean id="dataSource" class="com.spring.extensionPoint.DataSourceConfig">
<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>
测试方法:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
DataSourceConfig config= context.getBean("dataSource",DataSourceConfig.class);
System.out.println(config);
测试结果如下:
DataSourceConfig{driverClassName='com.mysql.jdbc.Driver', url='jdbc:mysql:mydb', username='sa', password='root'}
Spring 2.5后引入了context命名空间,可以专用的配置元素来配置属性占位符。多个地址可以使用逗号分隔符。
<context:property-placeholder location="classpath:extensionPoint/override.properties"/>
PropertyPlaceholderConfigurer不仅仅查看开发者指定的Properties文件。默认的话,它如果不能再指定的属性文件中找到属性的话,仍然会检查Java的System配置
。开发者可以通过配置systemPropertiesMode
属性来修改这个行为,这个属性有如下三种值:
nerver(0)
:从不检查系统属性
fallback(1)
:如果没有从指定的属性文件中找到特定的属性时检查系统属性,这个是默认值
。
override(2)
:优先查找系统属性,而后才试着检查指定配置文件。系统的属性会覆盖指定文件的属性。
接下来看看PropertyPlaceholderConfigurer的源码,先看下PropertyPlaceholderConfigurer的继承关系,发现有beanFactoryPostProcessor
在PropertyResourceConfigurer实现了BeanFactoryPostProcessor,那么postProcessBeanFactory就是主入口了,后面的流程有兴趣可以自己看看。
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//从特定的文件加载属性到mergedProps中
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
//这里的处理会properties属性,会生成一个解析器,然后注入到容器中,装载beand的时候再来回调这个解析器
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
另外一个例子是PropertyOverrideConfigurer
PropertyOverrideConfigurer,是另一个bean的后置处理器
,有些类似于PropertyPlaceholderConfigurer,但是区别于后者,原有的定义可能有默认值,或者没有任何值。如果覆盖的Properties文件没有一个确切的Bean属性,就使用默认的定义
。
覆盖的属性如下,注意但是要求每一个路径上的组件,需要是非空的,也就是dataSource该组件不能为空
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
在具体的xml配置如下,那么dataSource.driverClassName和url将被覆盖,没有覆盖的值保持原样
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations" value="classpath:extensionPoint/override.properties"/>
</bean>
在Spring 2.5引入的context命名空间中,也可以指定配置覆盖的文件属性如下:
<context:property-override location="classpath:extensionPoint/override.properties"/>
3.自定义FactoryBean的初始化逻辑
FactoryBean接口是一种类似于Spring IoC容器的插件化的逻辑
。如果当开发者的代码有复杂的初始化代码,在配置上使用Java代码比XML更有效时,开发者可以考虑创建自己的FacotoryBean对象,将复杂的初始化操作放到类中,将自定义的FactoryBean扩展到容器中
。
FacotryBean接口提供如下三个方法
:
Object getObject()
:返回一个工厂创建的对象。实例可被共享,取决于返回Bean的作用域为原型还是单例。
boolean isSingleton()
:如果FactoryBean返回单例,为True,否则为False
Class getObjectType()
:返回由getObject()方法返回的对象的类型,如果对象类型未知,返回null。
FactoryBean概念和接口广泛用预Spring框架,Spring本身就有多于50个FactoryBean的实现
。
当开发者需要一个FactoryBean实例而不是其产生的Bean的时候,在调用ApplicationContext的getBean()方法时,在其id之前加上&符号。也就是说,对于一个给定的FactoryBean,其id为myBean,调用getBean("myBean")返回其产生的Bean对象,而调用getBean("&myBean")返回FactoryBean实例本身
。
下面进行例子演示:
/**
* @Project: spring
* @description: 说明是一个factroyBean 是一个bean,但是是一个工厂bean
* @author: sunkang
* @create: 2018-09-16 16:34
* @ModificationHistory who when What
**/
public class FactoryBeanDemo implements FactoryBean {
@Override
public Object getObject() throws Exception {
return "factoryBean";
}
@Override
public Class<?> getObjectType() {
return String.class;
}
}
在spring-extensionPoint.xml具体的配置如下:
<bean id="factoryBean" class="com.spring.extensionPoint.FactoryBeanDemo"/>
测试如下:
ApplicationContext context = new ClassPathXmlApplicationContext("extensionPoint/spring-extensionPoint.xml");
//当其id为factoryBean,调用getBean("factoryBean")返回其产生的Bean对象,
//而调用getBean("&factoryBean")返回FactoryBean实例本身。
FactoryBeanDemo factoryBeanDemo = context.getBean("&factoryBean",FactoryBeanDemo.class);
System.out.println("得到工厂:factory"+factoryBeanDemo);
//得到bean对象,其实是工厂bean调用了getObject方法产生的对象
System.out.println("得到具体的bean:"+context.getBean("factoryBean"));
结果如下: 结果很显然
得到工厂:factorycom.spring.extensionPoint.FactoryBeanDemo@5025a98f
得到具体的bean:factoryBean