容器背后的秘密
除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功能,我们也应该想一下,Spring的IoC容器内部到底是如何来实现这些的呢?
如图4-7(该图摘自Spring官方参考文档)所示的那样,只告诉你“Magic Happens Here”,你能满意吗?如果不能满意的话那就一起来探索一下这个“黑匣子”里面到底有些什么…
“战略性观望”
Spring的IoC容器所起的作用,就像图4-7所展示的那样,它会以某种方式加载Configuration Metadata
(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段
和Bean实例化阶段
,如图4-8所示。
Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑。
1. 容器启动阶段
容器启动开始,首先会通过某种途径加载Configuration MetaData
。在大部分情况下,容器并不是通过直接编码方式来配置的,而是需要依赖某些工具类(Bean Definition Reader
)对加载的Configuration MetaData
进行解析和分析,并将分析后的信息编组为相应的BeanDefinition
,最后把这些保存了bean定义必要信息的BeanDefinition
,注册到相应的BeanDefinitionRegistry
,这样容器启动工作就完成了。图4-9演示了这个阶段的主要工作。
总地来说,该阶段所做的工作可以认为是准备性的,重点更加 侧重于对象管理信息的收集 。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。
2. Bean实例化阶段
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition
的方式注册到了BeanDefinitionRegistry
中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition
所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。
如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
插手“容器的启动”
Spring提供了一种叫做BeanFactoryPostProcessor
的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition
所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。
如果要自定义实现BeanFactoryPostProcessor
,通常我们需要实现org.springframework.beans.factory.config.BeanFactoryPostProcessor
接口。同时,因为一个容器可能拥有多个BeanFactoryPostProcessor
,这个时候可能需要实现类同时实现Spring的org.springframework.core.Ordered
接口,以保证各个BeanFactoryPostProcessor
可以按照预先设定的顺序执行(如果顺序紧要的话)。但是,因为Spring已经提供了几个现成的BeanFactoryPostProcessor
实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor
。
其中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
和org.springframework.beans.factory.config.PropertyOverrideConfigurer
是两个比较常用的BeanFactoryPostProcessor
。
另外,为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过org.springframework.beans.factory.config.CustomEditorConfigurer
来注册自定义的PropertyEditor
以替换容器中默认的PropertyEditor
。可以参考BeanFactoryPostProcessor
的Javadoc来了解更多其实现子类的情况。
我们可以通过两种方式来应用BeanFactoryPostProcessor
,分别针对基本的IoC容器BeanFactory
和较为先进的容器ApplicationContext
。
对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor ,下方代码演示了具体的做法。
如果拥有多个BeanFactoryPostProcessor
,我们可以添加更多类似的代码来应用所有的这些BeanFactoryPostProcessor
。
对于ApplicationContext
来说,情况看起来要好得多。因为ApplicationContext
会自动识别配置文件中的BeanFactoryPostProcessor
并应用它,所以,相对于BeanFactory
,在ApplicationContext
中加载并应用BeanFactoryPostProcessor
,仅需要在XML配置文件中将这些BeanFactoryPostProcessor
简单配置一下即可。只要如下方代码所示,将相应BeanFactoryPostProcessor
实现类添加到配置文件,ApplicationContext
将自动识别并应用它。
下面让我们看一下Spring提供的这几个BeanFactoryPostProcessor
实现都可以完成什么功能。
1. PropertyPlaceholderConfigurer
通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。
PropertyPlaceholderConfigurer
允许我们在XML配置文件中使用占位符(PlaceHolder
),并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。以数据源的配置为例,使用了PropertyPlaceholderConfigurer
之后(这里沿用上方的配置内容),可以在XML配置文件中按照下方代码所示的方式配置数据源,而不用将连接地址、用户名和密码等都配置到XML中。
所有这些占位符所代表的资源,都放到了jdbc.properties
文件中,如下所示:
基本机制就是之前所说的那样。当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}
、${jdbc.driver}
。当PropertyPlaceholderConfigurer
作为BeanFactoryPostProcessor
被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition
中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了。
PropertyPlaceholderConfigurer
不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可以通过setSystemPropertiesMode()
或者setSystemPropertiesModeName()
来控制是否加载或者覆盖System相应Properties的行为。
PropertyPlaceholderConfigurer
提供了SYSTEM_PROPERTIES_MODE_FALLBACK
、SYSTEM_PROPERTIES_MODE_NEVER
和SYSTEM_PROPERTIES_MODE_OVERRIDE
三种模式。默认采用的是SYSTEM_PROPERTIES_MODE_FALLBACK
,即如果properties文件中找不到相应配置项,则到System的Properties中查找,我们还可以选择不检查System的Properties或者覆盖它。更多信息请参照PropertyPlaceholderConfigurer
的Javadoc文档。
2. PropertyOverrideConfigurer
可以通过PropertyOverrideConfigurer
对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。
(这里不做具体介绍)
3. CustomEditorConfigurer
CustomEditorConfigurer
是另一种类型的BeanFactoryPostProcessor
实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition
没有做任何变动。
XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer
就是帮助我们传达类似信息的。
(这里不做具体介绍)