Spring IOC容器以某种方式加载配置文件,然后根据这些信息绑定整个系统的对象,最终组装成一个可用的容器系统.Spring IOC容器实现这些功能可以将流程划分为两个阶段,分别为容器启动阶段和Bean实例化阶段.
Spring在这两个阶段都加入了容器的扩展点以便我们根据场景的需要加入自定义的扩展逻辑
容器启动阶段
容器启动阶段任务
- 加载配置
- 分析配置信息
- 装配到BeanDefinition
容器启动时候会通过某种途径加载配置文件,容器首先通过BeanDefinitionReader对配置文件进行解析和分析,对分析后的信息封装为相应的BeanDefinition,最后把这些保存了Bean定义必要信息的BeanDefinition注册到相应的BeanDefinitionRegistry,这样容器的启动工作就完成了。总的来说在这一阶段容器做的事情就是收集bean的配置信息,对bean进行一些验证性或者辅助性的工作.Spring在这一阶段开放了一些扩展接口用于修改在第一阶段收集到的信息,接下来我们了解下容器启动阶段Spring IOC开放的扩展接口
BeanFactoryProcessor
该接口允许我们在容器实例化对象之前,对容器收集的Bean配置信息进行修改.因为一个容器可能拥有多个BeanFactoryPostProcessor,如果BeanFactoryPostProcessor执行顺序很重要的话,为了保证容器调用该接口的顺序我们还得实现优先级接口用于Bean的排序,优先级接口有两种分别如下
- org.springframework.core.Ordered
- org.springframework.core.PriorityOrdered
其中PriorityOrdered直接继承了Ordered接口且没有添加新的方法用于标识比Ordered实现类更为重要的bean,Spring的IOC容器在调用BeanFactoryProcessor实现类的时候分为两个阶段,第一个阶段是调用PriorityOrdered接口的实现类,第二个阶段是才是调用Ordered接口的实现类
在Spring中,Spring又是如何应用BeanFactoryProcessor接口的呢?
Spring已经提供了几个现成的BeanFactoryPostProcessor实现类,所以我们不需要自己实现BeanFactoryProcessor接口.
其中PropertyPlaceholderConfigurer,PropertyOverrideConfigurer,CustomEditorConfigurer是常用的Spring内部BeanFactoryPostProcessor接口实现类.接下来我们一一讲解这三个实现类
PropertyPlaceholderConfigurer
这个类在我们利用Spring连接数据库的时候都会用到,它允许我们在XML文件中使用占位符,并将这些占位符所代表的字眼单独配置到简单的properties文件来加载
在BeanFactory在加载xml配置文件所有的配置信息的时候,BeanFactory保存的对象属性信息还是以占位符的形式存在,比如${jdbc.url},当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用的时候,它会用properties配置文件的信息替代相应的BeanDefinition属性值,在bean真正实例化的时候所有的属性都已经替换完成了
PropertyPlaceholderConfigurer不仅可以从配置的properties文件中加载配置项,也可以利用java的System类中的Properties
在Spring3.1之后更推荐使用PropertySourcesPlaceholderConfigurer完成上述的功能并且比PropertyPlaceholderConfigurer 具有更大的灵活性
PropertyOverrideConfigurer
PropertyOverrideConfigurer可以覆盖容器中配置的任何你想处理的bean定义信息,比如spring xml配置的某个属性不合适,我们可以在Spring容器初始化的第一个阶段修改这个配置信息,这种覆盖对bean定义是透明的
下面举一个修改数据源的例子
Property覆盖文件,格式必须为beanid.propertyName = propertyValue
mainDataSource.driverClassName=com.mysql.jdbc.Driver
mainDataSource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
mainDataSource.username=root
mainDataSource.password=xxxxx
声明PropertyOverrideConfigurer bean
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="classpath:adjust/jdbc-adjust.properties"/>
</bean>
CustomEditorConfigurer
Spring已经内置了许多的PropertyEditor用于映射xml字符串数据类型到真正的数据类型,比如数组类型StringArrayPropertyEditor,映射类ClassEditor,文件类型FileEditor等等.上述的这些类型Spring容器会自动加载.如果上述的PropertyEditor不能满足我们的需求,我们可以自定义Editor映射数据
下面举一个将字符串转化为时间的例子
DateTimeEditor首先要继承PropertyEditorSupport类,这样就避免了直接重写BeanFactoryProcessor麻烦
public class DateTimeEditor extends PropertyEditorSupport {
//字符串时间的格式
private String datePattern="yyyy/MM/dd";
//在这个方法中将字符串时间转化为Date对象
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat(getDatePattern());
try {
Date date=simpleDateFormat.parse(text);
//设置时间对象
setValue(date);
} catch (ParseException e) {
throw new RuntimeException("时间配置错误");
}
}
public String getDatePattern() {
return datePattern;
}
public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}
}
编写时间注册器
public class DateEditorRegistry implements PropertyEditorRegistrar {
private PropertyEditor propertyEditor;
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(java.util.Date.class,getPropertyEditor());
}
public PropertyEditor getPropertyEditor() {
return propertyEditor;
}
public void setPropertyEditor(PropertyEditor propertyEditor) {
this.propertyEditor = propertyEditor;
}
}
向CustomEditorConfigurer注入我们的时间注册器
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="datePropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="datePropertyEditorRegistrar" class="com.spring.ioc.interfaces.propeditor.DateEditorRegistry">
<property name="propertyEditor">
<ref bean="datePropertyEditor"/>
</property>
</bean>
//自定义的时间解析器
<bean id="datePropertyEditor" class="com.spring.ioc.interfaces.propeditor.DateTimeEditor">
<property name="datePattern" value="yyyy/MM/dd"/>
</bean>
经过以上步骤之后,Spring就会将如下这样字符串转化为时间对象了
<bean id="user" class="com.spring.ioc.interfaces.propeditor.User">
<property name="name" value="tom"/>
<property name="birthDay" value="2017/8/30"/>
</bean>
Bean实例化阶段
Bean实例化阶段任务
- 实例化对象
- 装配依赖
- 生命周期回调
- 对象其它处理
- 注册回调接口
经过第一阶段后,所有的bean定义信息都封装为BeanDefinition注册到BeanDefinitionRegistry.当请求某个bean的时候,容器会首先检查所请求的对象是否被初始化了,如果没有容器会根据BeanDefinition所提供的信息实例化被请求的对象并注入依赖,如果该对象实现了某些回调接口,容器也会根据接口的要求装配它,然后将对象返回给请求方使用
下面是整个bean实例化的整个过程,一图胜千言,接下来便是一一讲解这个图中展示的所有阶段
接下来一一讲解上图的每个过程
实例化对象与属性设置
Spring在实例化对象的时候利用反射或者CGLIB的方式初始化相应的bean实例,其中InstantiationStrategy是实例化策略接口,它的实现类SimpleInstantiationStrategy可以利用反射来实例化对象,而CglibSubclassingInstantiationStrategy是SimpleInstantiationStrategy的子类可以通过方法注入的方式实例化对象,容器默认采用CglibSubclassingInstantiationStrategy方式实例化对象.容器只要根据相应bean定义的BeanDefinition取得实例化信息,结合相应的实例化策略完成对象实例化,但是实例化返回的不是所需要的对象类型,而是被BeanWrapperImpl包装过的,目的在于避免直接操作java 反射API便于对象属性的设置,先前我们讲过许多的PropertyEditor和我们自定义的时间属性解析器也是在这一阶段使用的
Aware型接口
Aware型接口是实例化完成并且相关属性依赖设置完成之后,Spring容器会检查当前对象实例是否实现一系列Aware命名结尾的接口定义,如果是则将这些接口定义中的依赖注入到当前对象实例
BeanFactory容器中常用Aware型接口如下所示
org.springframework.beans.factory.BeanNameAware
org.springframework.beans.factory.BeanClassLoaderAware
org.springframework.beans.factory.BeanFactoryAware
ApplicationContext容器中常见Aware型接口
org.springframework.core.io.ResourceLoader
org.springframework.context.ApplicationEventPublisherAware
org.springframework.context.MessageSourceAware
org.springframework.context.ApplicationContextAware
BeanPostProcessor
BeanPostProcessor和上文的BeanFactoryProcessor是Spring中两个重要的接口,同时它们也很容易被混淆
BeanPostProcessor接口定义如下
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
BeanPostProcessor会处理容器内【所有】符合条件的实例化后的对象实例,该接口定义了一个可以自己实现的回调方法,来实现自己的实例化逻辑,依赖解决,我们可以在Spring完成对象【实例化之后】实现自己的初始化逻辑,其中postProcessBeforeInitialization方法是在所有的bean初始化的时候afterPropertiesSet方法之前执行,postProcessAfterInitialization是所有的bean的afterPropertiesSet方法之后执行的。
Spring内部中使用了BeanPostProcessor接口实现类ApplicationContextAwareProcessor用于处理标记接口实现类比如上文提到的Aware型的接口,具体流程是这样的,ApplicationContext对应的每个对象实例化走到BeanPostProcessor前置这一步的时候,利用之前注册到容器的BeanPostProcessor实现类ApplicationContextAwareProcessor调用postProcessBeforeInitialization这个方法来实现每个Aware接口的相应功能
自定义BeanPostProcessor接口在bean实例化后解密加密过后的字符串
密码解码器
public class PasswordDecodedProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//选择在afterPropertySet之后使用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//如果这个类实现了PasswordDecodable接口
if (bean instanceof PasswordDecodable) {
//获取到加密过的密码
String encodedPassword = ((PasswordDecodable) bean).getEncodedablePassword();
//解码
String decodedPassword = decodePassword(encodedPassword);
((PasswordDecodable) bean).setDecodedPassword(decodedPassword);
}
return bean;
}
public String decodePassword(String encodedPassword) {
return new String(Base64.decodeBase64(encodedPassword));
}
}
密码解码接口声明
public interface PasswordDecodable {
String getEncodedablePassword();
void setDecodedPassword(String psd);
}
密码解码接口实现类
public class DBConfig implements PasswordDecodable {
private String dbuser;
private String psd;
@Override
public String getEncodedablePassword() {
return psd;
}
@Override
public void setDecodedPassword(String psd) {
this.psd = psd;
}
public void connectToDB() {
System.out.println(String.format("uaing role -> %s , psd -> %s , connecting ...", dbuser, psd));
}
public String getDbuser() {
return dbuser;
}
public void setDbuser(String dbuser) {
this.dbuser = dbuser;
}
public String getPsd() {
return psd;
}
public void setPsd(String psd) {
this.psd = psd;
}
}
容器中声明该类
<bean id="dbConfig" class="com.spring.ioc.interfaces.beanprocessor.DBConfig">
<property name="dbuser" value="root"/>
<!--加密过后的密码-->
<property name="psd" value="MTIzNDU2"/>
</bean>
测试
@Test
public void test2() {
DBConfig config = (DBConfig) context.getBean("dbConfig");
System.out.println(config.getPsd());
}
就可以发现密码被解密了
InitializingBean和init-method
在BeanPostProcessor完成前置方法后,就会调用InitializingBean实现类的afterPropertiesSet()方法,InitializingBean接口在Spring容器中广泛应用比如TransactionTemplate实现InitializingBean接口用于判断transactionManager是否已经初始化,如果没有则抛出异常。源码如下:
@Override
public void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
我们也可以利用init-method代替InitializingBean接口完成相同的功能
总的来说实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖
DisposableBean与destroy-method
容器会检查singleton类型的bean,若其实现了DisposableBean接口.或者实现了destroy-method指定的方法,如果是Spring容器就会注册一个用于对象销毁的回调,便于在对象销毁之前执行销毁逻辑.在注册回调后Bean就处于使用状态,还没有完,我们还没有告诉Spring容器什么时候执行bean的销毁方法,对ApplicationContext容器来说,了AbstractApplicationContext提供了shutdownHook方法用于告知java虚拟机退出之前执行这个方法
如下实例
public class DestoryBean implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("destory singleton instance -> " + getClass());
}
}
向容器中注册
<bean id="destoryBean" class="com.spring.ioc.interfaces.destory.DestoryBean"/>
测试
@Test
public void destory() {
DestoryBean bean = (DestoryBean) context.getBean("destoryBean");
context.registerShutdownHook();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
可以发现在调用registerShutdownHook方法后销毁方法没有被马上调用,而是在sleep后也就是虚拟机退出时调用