容器启动阶段我们可以其实可以偷偷做一些事情
书接上文:细说Spring——IoC详解(一),我们已经知道了容器实现控制反转和依赖注入的过程可以分为两个阶段:
- 容器启动阶段
-
Bean
的实例化阶段
其实在这个两个阶段我们都可以偷偷的做一些事情,我们可以根据具体的场景加入自定义的扩展逻辑,下面我们就来了解一下容器启阶段我们可以做哪些事情。
Spring
提供了一种叫做BeanFactoryPostProcessor
的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition
所保存的信息做相应的修改。我们已经知道BeanDefinition
中保存了创建一个对象所需要的所有信息,那么既然我们可以修改BeanDefinition
所保存的信息,那么是不是我们就可以为所欲为了,哈哈,比如我们可以修改其中bean
定义的某些属性,为bean
定义增加其他信息等,想想就很激动呢。那我们就接着看看怎么使用BeanFactoryPostProcessor
容器扩展机制吧。
这里要把BeanFactory
和ApplicationContext
两种容器分开来讲:
- 首先是
BeanFactory
,我们也知道BeanFactory
是ApplicationContext
的父类,那么功能上BeanFactory
也是比较弱小的,我们需要使用手动写代码来应用BeanFactoryPostProcessor
,例如:
// 声明将被后处理的BeanFactory实例
ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("配置文件地址"));
// 声明要使用的BeanFactoryPostProcessor
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("jdbc.properties"));
// 执行后处理操作
propertyPostProcessor.postProcessBeanFactory(beanFactory);
- 接着是更高级的
ApplicationContext
容器,这个就牛逼多了,他可以自动识别容器中的BeanFactoryPostProcessor
实例对象,并使用他们,是的,是“他们”,我们可以在一个容器中使用多个BeanFactoryPostProcessor
,这个时候聪明的你就可能想到顺序的问题,我们先买个关子,这里还是说怎么在ApplicationContext
容器中使用BeanFactoryPostProcessor
,我们其实只要把BeanFactoryPostProcessor
的实现类配置到配置文件中就可以了(额,突然想到好像也应该把xml
配置文件讲一下,这个还是先挖个坑把)如下所示:
<beans>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
<value>conf/mail.properties</value>
</list>
</property>
</bean>
</beans>
是不是简单多了,果然还是高级的好啊。接下来就让我们看看BeanFactoryPostProcessor
到底哪里神通广大吧。在我们学习要自定义实现BeanFactoryPostProcessor
之前,我们可以先来看看为Spring
已经提供的几个现成的BeanFactoryPostProcessor
实现类:
1、PropertyPlaceholderConfigurer
在我们没有学习过PropertyPlaceholderConfigurer
之前,我们在写xml
配置文件的时候可能需要将一些可能改变的数据写的xml
配置文件中,比如数据库连接信息、邮件服务器等相关信息,这些信息可能在后来发生改变,这样直接写到xml
配置文件中肯定是有问题的,如果我们可以将这些可能会变的信息写到另外一个文件中就好了。这个时候就可以使用到PropertyPlaceholderConfigurer
了。
PropertyPlaceholderConfigurer
允许我们在XML
配置文件中使用占位符,并将这些占位符所代表的资源单独配置到简单的properties
文件中来加载。以数据源的配置为例,使用
了PropertyPlaceholderConfigurer
之后,我们就可以像下面这样写配置文件:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
<bean>
那么这些占位符所表示的资源我们存放在哪里呢?,还记得我们上面写的
propertyPostProcessor.setLocation(new ClassPathResource("jdbc.properties"));
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
<value>conf/mail.properties</value>
</list>
</property>
吗?这里的jdbc.properties
就是存放占位符所表示资源的地方,你不会告诉我你没有见过properties
文件吧,那你可得赶紧好好学习了。下面是jdbc.properties
文件的内容:
jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=your username
jdbc.password=your password
PropertyPlaceholderConfigurer
的实现机制就是当BeanFactory
在第一阶段加载完成所有配置信息时,BeanFactory
中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}
、${jdbc.driver}
。当
PropertyPlaceholderConfigurer
作为BeanFactoryPostProcessor
被应用时,它会使用properties
配置文件中的配置信息来替换相应BeanDefinition
中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean
时,bean
定义中的属性值就是最终替换完成的了。有了这样好用的工具,是不是感觉生活都变得美好了。接下来我们来看第二个BeanFactoryPostProcessor
。
2、PropertyOverrideConfigurer
比起我们第一个学习的PropertyPlaceholderConfigurer
,这个PropertyOverrideConfigurer
确实是在偷偷地做一些事情,我们可以通过PropertyOverrideConfigure
r对容器中配置的任何你想处理的bean
定义的property
信
息进行覆盖替换,这里还是举个例子吧,还是前面的数据源的配置文件为例。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="driverClassName">
<value>${jdbc.driver}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="testOnBorrow">
<value>true</value>
</property>
<property name="testOnReturn">
<value>true</value>
</property>
<property name="testWhileIdle">
<value>true</value>
</property>
<property name="minEvictableIdleTimeMillis">
<value>180000</value>
</property>
<property name="timeBetweenEvictionRunsMillis">
<value>360000</value>
</property>
<property name="validationQuery">
<value>SELECT 1</value>
</property>
<property name="maxActive">
<value>100</value>
</property>
</bean>
这里面有minEvictableIdleTimeMillis
、timeBetweenEvictionRunsMillis
两个属性,假设我们按照如下代码把将PropertyOverrideConfigurer
加载到容器之后,dataSource
原来定义的默认值就会被pool-adjustment.properties
文件中的信息所覆盖:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
# pool-adjustment.properties 11
dataSource.minEvictableIdleTimeMillis=1000
dataSource.timeBetweenEvictionRunsMillis=50
这里需要注意我们在写PropertyOverrideConfigurer
配置文件的时候,一定是是以XML
中配置的bean
定义的beanName
为标志开始的(通常就是id
指定的值)比如上面pool-adjustment.properties
中的dataSource.minEvictableIdleTimeMillis=1000
,后面跟着相应被覆盖的property
的名称。
3、CustomEditorConfigurer
我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML
文件格式来配置这些对象类型。但XML
所记载的,都是String
类型,即容器从XML
格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer
就是帮助我们传达类似信息的。
在了解怎么使用CustomEditorConfigurer
之前,我们需要先了解PropertyEditor
,那么PropertyEditor
到底是何方神圣呢?
其实PropertyEditor
就是那个真正干活的人,就是他实现了具体的类型转换逻辑,而CustomEditorConfigurer
更像是一个领导,通常不都是领导动动嘴,下属跑断腿,哈哈,这里PropertyEditor
就是干活的下属。
Spring
中提供了很多的PropertyEditor
,对应了各种转换逻辑,这里就不一一列举了,如果想研究一下,可以去org.springframework.beans.propertyeditors
下面研究。这里着重讲解一下怎么实现自定义的PropertyEditor
4、实现自定义的PropertyEditor
首先我们先来看一看PropertyEditor
接口的定义:
public interface PropertyEditor {
void setValue(Object value);
Object getValue();
boolean isPaintable();
void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box);
String getJavaInitializationString();
String getAsText();
void setAsText(String text) throws java.lang.IllegalArgumentException;
String[] getTags();
java.awt.Component getCustomEditor();
boolean supportsCustomEditor();
void addPropertyChangeListener(PropertyChangeListener listener);
void removePropertyChangeListener(PropertyChangeListener listener);
}
不要被这么多的方法给吓到,对于我们使用者来说其实重点只有下面4个方法:
-
void setValue(Object value);
设置属性值 -
Object getValue();
获取属性值 -
String getAsText();
把属性值转换成String
-
void setAsText(String text);
把String
转换成属性值
所以Java
很机智地提供了一个适配器java.beans.PropertyEditorSupport
来帮助我们实现属性值的转换,它帮助我们实现了大部分的方法,我们只需要重写getAsText
和setAsText
的逻辑就可以了,哈哈,又可以偷懒了。
这里我就也偷个懒,我在学习PropertyEditor
的时候发现了一篇讲解的特别好的博客,里面有自定义PropertyEditor
的例子,还包括了一下我本来不是很清楚的知识,这里大家就去看看这个博客就可以了:Spring PropertyEditor分析
5、最后填一个坑
还记得我说过的一个容器可以有多个BeanFactoryPostProcessor
吗,还记得我并没有说如果有多个BeanFactoryPostProcessor
时的执行顺序问题,这里就解答一下。
这里首先要提出一个Ordered
接口,这个接口的作用就是指定操作顺序的,下面来看一下这个接口的定义:
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
只有1个方法:getOrder();
2个变量:最高级(数值最小)和最低级(数值最大)。
这里我们可以看出来数值越小,那么对应的优先级就越大。
我们如果有时间,可以去看一下前面讲的PropertyPlaceholderConfigurer
、PropertyOverrideConfigurer
、CustomEditorConfigurer
三个PropertyPlaceholderConfigurer
的源码,就会发现他们都直接或间接的实现了Ordered
接口,也就是说,我们可以通过指定其order
属性的值,为这些PropertyPlaceholderConfigurer
安排顺序,这样我们就再也不用担心顺序的问题了,同时我们自定义的PropertyPlaceholderConfigurer
也可以通过实现Ordered
接口,来指定顺序。
第二篇也结束了,请继续关注接下的内容。
网友评论