容器背后的密码
Spring的IoC容器所起的作用,它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器的启动阶段和Bean的实例化阶段。
Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了容器的扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。
1、容器启动阶段
加载配置.......
分析配置信息......
装备到BeanDefinition......
其他后处理......
容器启动伊始,会通过某种途径加载Configuration MetaData。容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息组编为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry。这样容器的启动工作就完成了。
2、Bean实例化阶段
在该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,那会根据注册的BeanDefinition所提供的信息实例化被请求的对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。实例化对象......
装配依赖......
生命周期回调......
对象其他处理......
注册回调接口......
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一个阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作。
如果需要自定义实现BeanFactoryPostProcessor,通常我们需要实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。Spring已经提供了几个 现成的BeanFactoryPostProcessor实现类,所以,很少时候需要我们自己动手实现某个BeanFacotryPostProcessor。其中,org.springframework.beans.factory.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。
对于BeanFactory来说,我们需要用手动方式应用到所有的BeanFactoryPostProcessor:
ConfigurableListableBeanFactory beanFatory = new XmlBeanFactory(new ClassPathResource("..."));
PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
propertyPostProcessor.setLocation(new ClassPathResource("..."));
propertyPostProcessor.postProcessBeanFactory(beanFactory);
由于ApplicationContext来说,ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它,仅需要在XML配置文件中将这些BeanFactoryPostProcessor简单配置一下即可:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>conf/jdbc.properties</value>
<value>conf/jmail.properties</value>
</list>
</property>
</bean>
PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符(PlaceHolder),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。
当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象属性信息还是以占位符的形式存在。当ProperyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应的BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成了。
PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查java的System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应的Properties的行为。
PropertyOverrideConfigurer对容器中配置的任何bean信息的property信息进行覆盖替换。例如,我们配置了dataSource bean的maxActive的值为100,我们可以通过PropertyOverrideConfigurer在其相应的properties文件中做如下的配置,就可以把100这个值给覆盖掉,如将其值配置为200:
dataSource.maxActive=200
如果需要对容器中的某些bean定义的property信息进行覆盖,我们需要按照如下规则提供一个PropertyOverrideConfigurer使用的配置文件:
beanName.propertyName=value
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="localhost" value="pool-adjustment.properties" />
</bean>
pool-adjustment.properties中没有提供的配置项将继续使用原来XML配置中的默认值。
当容器中配置的多个PropertyOverrideConfigurer对同一个bean定义的同一个property值进行处理的时候,最后一个将会生效。
配置在properties文件中的信息通常都以明文表示,PropertyOverrideConfigurer的父类PropertyOverrideConfigurer提供了一个protected类型的方法convertPropertyValue,允许子类覆盖这个方法对相应的配置项进行转换,如对加密后的字符串解密之后再覆盖到相应的bean定义中。当然,既然PropertyPlaceholderConfigurer也同样继承了PropertyResourceConfigurer,我们也可以针对PropertyPlaceholderConfigurer应用类似的功能。
Bean的一生
容器启动后,并不会马上就实例化相应的bean定义。容器现在仅仅拥有所有对象的BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方通过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。getBean()方法可以被客户端显示的调用,也可以在容器内被隐式的调用:
- 对于BeanFactory来说,对象实例化默认采用延迟加载初始化。当对象A被请求而需要第一次实例的时候,rugged它所依赖的对象B之前同样没有被实例化,那么容器会先实例化对象A所依赖的对象。这种情况是容器内部的隐式调用getBean()方法。
- ApplicationContext启动之后会实例化所有的bean定义,但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段,只不过他会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。