在JavaEE开发中,基于Spring开发是最目前最常见的方式. Spring框架不但提供了IoC, AOP等支持,在此基础上,还提供了一系列非常简单好用的模块,具体可以Spring官网上的说明. 作为Spring框架的基础,IoC机制使得整个应用程序的各个基础元素间依赖关系的管理变得更加灵活易用,这里我们不对IoC机制的定义进行过多的阐述,而是试图从源码的角度来分析,Spring框架是如何实现所谓的”依赖反转”.
0. 一切都要从BeanFactory说起
BeanFactory接口作为整个Spring容器的最基础的接口,它定义了一系列的管理bean组件的方法,具体如下图所示, 从图中可以看出,这个接口中定义了从容器中获取bean的方法getBean, 还定义了判断bean的作用域(scope)的方法, isPrototype和isSingleton, 除此之外,它还提供了方法来判断容器是否含有指定元素containsBean方法. 一般情况下,在应用程序中,beanFactory都是以单例模式中实现,获取到这个容器对象后,就可以通过它来管理应用程序中所有的bean对象以及它们之间的依赖关系.
再来看看BeanFactory接口的类层次图,如下图所示,可以看到这个接口的子接口和实现有很多,其中最重要的接口为ApplicationContext接口,按Spring官方文档中的说法,ApplicatonContext接口提供了比BeanFactory更丰富的功能,包括国际化支持,资源获取,事件发布机制以及层次化的context结构.
1. 程序的入口
为了简化程序的代码,我们定义了TestController和TestService, 前者依赖于后者, 具体的代码如下, 从这里也可以看出,Spring框架自动管理了applicationContext.xml中定义的bean对象以及它们之间的依赖关系.
|
|
接下来我们就来看看Spring是如何做到这一切的.
1.1 初始化
在上面的代码中,首先初始化了ApplicationContext接口的实例,在这里使用了ClassPathXmlApplicationContext类,除此之外,还可以使用FileSystemXmlApplicationContext, 两者的区别在于加载applicationContext.xml资源时的路径不同, ClassPathXmlApplicationContext类是从classpath目录下查找,而FileSystemXmlApplicationContext类是从文件系统中查找. 首先来看下ClassPathXmlApplicationContext的构造函数, 如下所示,在构造函数中首先设置了配置文件的路径,然后调用了refresh()方法, 可以这么说,refresh()方法实现了Spring框架中IoC机制的一大半功能,在这个方法中,完成了Bean对象的加载.
|
|
那就来看看refresh()方法的实现, 源码如下所示,实现的内容很多,但整个逻辑非常地清楚(由衷地对作者表示佩服),整个refresh()方法大体上可以分为以下这些步骤:
- 更新前的准备工作(prepareRefresh)
- 读取Bean对象的定义(obtainFreshBeanFactory)
- 初始化更新后的BeanFactory对象(prepareBeanFactory)
- 生成BeanFacotry对象后的处理工作(postProcessBeanFactory)
- 触发BeanFactoryPostProcessor接口(invokeBeanFactoryPostProcessors)
- 注册BeanPostProcessor接口(registerBeanPostProcessor)
- 初始化MessageSource(initMessageSource)
- 初始化事件发布器(initApplicationEventMulticaster)
- 触发refresh事件(onRefresh)
- 注册ApplicationEvent事件监听器(registerListeners)
- 结束BeanFactory的初始化工作(finishBeanFactoryInitialization)
- 结束refresh操作(finishRefresh)
- 清理缓存(resetCommonCaches)
|
|
1.1.1 prepareRefresh
首先来看下prepareRefresh方法的实现,作为refresh()方法中的第一个步骤,这个方法主要的功能就是完成更新前的准备工作, 如记录更新开始的时间,更新容器的状态,设置环境等, 其中最重要的是完成PropertySource的初始化,默认情况下,initPropertySources()方法是个空实现,但是它给子类提供了定制化初始化过程的机会,这也可以认为是“模板模式”的应用(严格来讲,模板模式中的方法是抽象方法,但思想是一样的), 在阅读Spring框架源码的过程中,可以发现有很多设计模式的实践,在遇到时会再次提及.
|
|
1.1.2 obtainFreshBeanFactory
这个方法中调用了refreshBeanFactory方法,后者完成了整个容器的Bean对象的加载工作,同样,这里的refreshBeanFactory方法也是个抽象方法,它的实现由子类提供,由于我们这里使用的是ClassPathXmlApplicationContext类,因此这个方法的具体实现由AbstractRefreshableApplicationContext类实现. 可以看到,refreshBeanFactory方法的步骤有:
- 判断当前是否已经有容器了,如果存在 ,则先删除原先容器中的对象,再关闭之前的容器(hasBeanFactory)
- 接着创建新的容器,并完成容器的初始化工作(createBeanFactory & customizeBeanFactory)
- 使用新的容器来加载和管理bean对象的定义(loadBeanDefinitions)
- 将新建的容器对象赋给ApplicationContext
|
|
判断是否已经有BeanFactory以及关闭的操作相对比较简单,这里就略过,直接开始看创建新容器以及初始化的操作,可以看到这里生成的是DefaultListableBeanFactory对象,并在生成后根据配置设置参数allowBeanDefinitionOverriding和allowCircularReferences, 前者代表了是否允许重载bean的定义,后者定义了是否允许循环依赖. DefaultListableBeanFactory类是可以直接被使用的BeanFactory实现(不包括ApplicationContext接口的实现).
|
|
接下来就是使用新的容器来加载和管理Bean对象的定义了. 在Spring框架中,bean对象的运行时定义是通过BeanDefinition类来实现的,这个类中定义了关于Bean对象的所有信息,包括构造方法参数,属性值,初始化方法,静态工厂方法等等信息. BeanDefinition之间还可以有继承关系,ChildBeanDefinition继承了ParentBeanDefinition的信息,也可以重写相应的属性. 在spring中,加载bean的信息就是通过这里的loadBeanDefinitions()方法来完成的, 同样,这里的loadBeanDefinitions方法又是个抽象方法,ClassPathXmlApplicationContext类中是通过AbstractXmlApplicationContext类中的方法来完成的,从这里也可以看出,Spring框架中大量使用了”模板模式”,从而将实现延迟到了具体的子类中,大大提高了框架的灵活性,这点在开发中,尤其是在框架开发中非常重要.
在loadBeanDefinitions的实现中,首先定义了XmlBeanDefinitionReader类,它是BeanDefinitionReader接口的实现,主要用于从xml文件中读取配置信息,接着就是配置这个xmlBeanDefinitionReader对象,然后做些初始化的工作,这里值得注意的是,设置了BeanDefinitionReader对象中的ResourceLoader为当前对象,即XmlClassPathApplicationContext对象. 最后就是使用这个xmlBeanDefinitionReader来读取配置文件了.
从BeanDefinitionReader的实现中可以看出,它是一个个读取Resource文件和configLocations文件,其中,对于之前的配置来讲,Resource文件为空,而configLocation文件则是在构造ClassPathXmlApplicationContext对象时传入的参数值,即applicationContext.xml,因此BeanDefinitionReader会尝试从这些文件中获取相应的Bean信息.
|
|
那么就来看看XmlBeanDefinitionReader是如何来加载Bean信息的. 代码虽然很长,但逻辑却很清楚,首先是获取到ResourceLoader对象,上面提及过,这里的ResourceLoader其实就是ClassPathXmlApplicationContext本身,然后通过这个对象读取location参数指定的资源信息,最后BeanDefinitionReader再从资源中读取相应的Bean定义信息.
|
|
那么再来看看BeanDefinitionReader又是如何从Resource资源对象中读取bean对象的定义的呢?这个方法的实现由XmlClassPathApplicationContext提供,它首先将Resource文件以Xml的格式读取,并形成相应的DOM, 然后调用registerBeanDefinitions()方法来注册bean. 在这个方法中,首先创建了一个BeanDefinitionDocumentReader对象,然后通过这个documentReader来加载Bean.
|
|
再来看看这个documentReader又是怎么做的,中间的过程代码这里就直接忽略了,总的来讲documentReader对象在registerBeanDefinitions方法中调用了doRegisterBeanDefinitions()方法,后者完成了加载工作. 这里又可以看出Spring框架中方法命名的一个特点,基本上某个方法如果以do开始,那么这个方法就是实际完成工作的地方,而与它对应的那个方法只是做些协调配置的工作.
在doRegisterBeanDefinition方法中,首先创建了个代理对象,在创建这个代理类的过程中,对一些默认值属性值进行了初始化,包括default-lay-init, default-merge, default-autowire等等属性,这些属性都是在
|
|
在完成delegate对象的初始化后,就由这个代理对象来解析bean的定义. 在这个方法中,首先判断根结点是否是默认的命名空间(即beans元素的命名空间http://www.springframework.org/schema/beans). 如果是则依次遍历根结点下的每个子结点,分别判断这些结点的命名空间,默认命名空间则调用parseDefaultElement()方法来解析,否则调用parseCustomElement()方法来解析.
parseDefaultElemet()方法是用来处理默认命名空间中的元素,它只负责处理四类子结点,分别是import, alias, bean以及嵌套的beans结点. 而parseCustomElement()方法是用来解析非默认命名空间中定义的结点,如context,mvc等等,对于这些自定义的命名空间,首先调用NamespaceHandlerResolver类的resolve()方法获取命名空间与实际的NamespaceHandler之间的对应关系,根据命名空间获取相应的解析器,并由解析器完成相应的解析工作, 如context命名空间的解析器为NamespaceHandlerResolver类. 由于自定义的命名空间不胜枚举,这里就不再进行详细阐述,有需要的时候可以查看具体的实现.
|
|
到此为止,就基本上完成了bean信息的加载过程,obtainFreshBeanFactory方法的执行也基本结束了,总结来看,容器类先是生成了XmlBeanDefinitionReader对象来读取Bean对象的定义,后者又将这个过程委托给BeanDefinitionDocumentReader来完成,而documentReader又委托给了BeanDefinitionParserDelegate,而这个delegate对象中真正完成了bean对象的加载.
|
|
1.1.3 prepareBeanFactory
可以看到,prepareBeanFactory方法首先设置beanFactory的属性值,包括ClassLoader, BeanExpressionResolver, PropertyEditorRegistrar, 然后增加了ApplicationContextAwareProcessor这个Aware接口,这就是为什么Spring框架中可以允许自动注入ApplicationContext对象的原因了.
接着,分别调用了ignoreDependencyInterface和registerResolvableDependency方法,前者的作用是忽略相应的接口依赖,这些接口会通过其它的方式完成注入,后者往容器中配置了依赖注入规则,当遇到这些类的依赖时,直接使用指定的对象, 例如需要依赖BeanFactory时,直接使用新生成的容器对象.
最后,prepareBeanFactory方法就会在容器查找框架特定的类定义,如loadTimeWeaver, environment, systemProperties, systemEnvironment等类,如果找到这些Bean的定义,则先实例化这些对象.
|
|
1.1.4 postProcessBeanFactory
在AbstractApplicationContext中,这个方法的实现是空实现,但它给子类一个机会,在完成容器的更新后进行一些定制化的操作,例如在AbstractRefreshableWebApplicationContext的实现中,就会在这个方法中注册ServletContextAwareProcessor接口.
|
|
1.1.5 invokeBeanFactoryPostProcessors
从方法名就可以看出,这个方法的作用是触发容器中定义的BeanFactoryPostProcessor接口. BeanFactoryPostProcessor接口可以在容器加载完bean信息后对它们的属性进行修改,如进行属性值的替换等操作. Spring文档中告诉我们,框架会自动识别实现了BeanFactoryPostProcessor接口的bean对象,其中的秘密就在这里.
|
|
这里主要来看下invokeBeanFactoryPostProcessors的执行过程. 这里的逻辑要分成两大部分:
- 处理当前beanFactory中已经有的beanFactoryPostProcessor: 这部分已经通过方法的第二个参数传入. 在处理这部分接口对象时,首先要区分当前的BeanFactory对象是否是BeanDefinitionRegistry接口的实现,如果是, 则除了要处理BeanFactoryPostProcessor接口,还需要找到这些BeanFactoryPostProcessor中实现了BeanDefinitionRegistryPostProcessor接口的接口,并执行该接口的方法,否则直接遍历执行BeanFactoryPostProcessor接口即可.
- 处理当前beanFactory中定义的beanFactoryPostProcessor接口: 这部分的处理和第一部分基本上类似,但需要考虑PriorityOrdered, Ordered接口的优先性,先处理实现了PriorityOrdered的接口,再处理实现了Ordered的接口,然后是普通的接口.
|
|
1.1.5 registerBeanPostProcessors
顾名思义,registerBeanPostProcessors方法就是往容器中注册BeanPostProcess接口,作为Spring容器的第二个扩展点,BeanPostProcessor接口允许应用程序对bean对象的实例化过程进行定制,例如,可以根据配置信息在实例化完成后产生相应的代理对象等等,事实上,Spring框架中提供的AOP机制就是通过BeanPostProcessor来实现的.
具体来看看这个方法实现的过程. 总体来讲和之前的实现差异不大,仍然是按PriorityOrdered,Ordered以及普通接口分成三类,然后分别往容器中按一定的顺序注册BeanPostProcessor接口.
到此为止,Spring容器已经完成了bean定义的加载,BeanFactoryPostProcessor接口的处理,并往容器中注册了BeanPostProcessor接口.
|
|
1.1.6 initMessageSource
Spring框架关于国际化的支持主要是通过MessageSource接口来完成的,它允许应用程序根据不同的地区显示不同的消息,在initMessageSource方法中,首先查看容器中是否定义了MessageSource接口的实现,如果没有定义这个接口的实现,则使用默认的MessageSource实现DelegatingMessageSource.
|
|
1.1.7 initApplicationEventMulticaster
这个方法主要是负责容器的ApplicationEventMulticaster对象的初始化工作,实现的逻辑与上面基本一致 ,首先判断容器中是否已经定义了这个对象,如果没有则使用默认的实现. 在Spring框架中,ApplicationEventMulticater接口主要应用程序事件的广播,可以往容器中注册ApplicationEvent接口及ApplicationListener接口,两者分别代表了事件及事件监听器,当发生相应的事件后,就可以通过ApplicationEventMulticaster接口来通知相应的监听器进行处理.
|
|
1.1.8 onRefresh
onRefresh()方法作为容器初始化过程中的”钩子”方法,允许子类在容器完成初始化后进行一些定制化的操作,如注册一些特定的对象等. 默认情况下为空实现.
1.1.9 registerListeners
在上面已经提到过,Spring容器支持事件的触发与广播,主要是通过ApplicationEventMulticater来实现的,但前提是容器中必须注册相应的事件及监听器,而这一步就是在这里完成. 这里首先获取容器中注册的ApplicationListener接口对象,在ApplicationEventMulticaster中完成注册,在注册完成后,检查初始化过程中是否已经有事件产生,如果有,则直接触发这些事件.
|
|
1.1.10 finishBeanFactoryInitialization
finishBeanFactoryInitialization方法中主要完成容器初始化完成后的收尾工作,包括配置ConversionService, 注册LoadTimeWeaverAware接口,冻结配置以及初始化对象实例等操作. ConversionService是Spring容器中进行类型转换的入口, 所有涉及到类型转换的场景, 如配置文件中的值到属性类型间的转换,都是从ConversionService完成的. 除此之外,这个方法中还对当前的配置进行了冻结,以示可以进行缓存操作.
|
|
在finishBeanFactoryInitialization方法的最后一步,调用了preInstaniateSingletons()方法. Spring文档中告诉我们,对于作用域为singleton的对象,lazy-init属性默认为false, 意味着容器总是会“预先”初始化这些对象,而这步就是在preInstantiateSingletons()这个方法中完成的.
这又是一个抽象方法,它的实现由DefaultListableBeanFactory类提供. 可以看到,预先初始化的对象需要满足三个条件:
- 不是抽象类
- scope为singleton
- lazyInit属性为false
在满足了以上三点后,容器就会尝试初始化这些singleton对象,首先判断这个类定义是否是FactoryBean,如果是的话,则再次判断它的eagerInit属性,只要在这个属性为true的情况下才开始实例化操作. 如果这个bean定义是普通的对象,则直接开始实例化操作. 对象的实例化操作都是通过getBean()方法来完成的,关于这个方法会在本文的第二部分进行详细的阐述,这里就不做叙述.
在完成对象的实例化操作后, 还会调用实现了SmartInitializingSingleton接口的对象的回调方法.
|
|
1.1.11 finishRefresh
到这里,容器的初始化工作已经基本完成,这个方法主要是完成LifecycleProcessor接口的配置与调用,并发布ContextRefreshEvent事件. LifecycleProcessor接口主要是负责与生命周期相关的方法,与之前一些接口的配置过程类似,这里也是先在容器中查询是否有相应的接口定义,如果没有则使用默认的实现. 在配置完LifecycleProcessor接口后,就会调用其onRefresh()方法,并发布ContextRefreshEvent事件. 这里值得注意的是getBean()方法,关于这个方法的实现过程会在本文的第二部分中进行详细阐述, 这里只要知道它是从容器中获取相应的实例对象即可.
|
|
到此为止,示例代码中构造ApplicationContext对象的源码就已经全部解读完毕了,总得来讲,在构造函数中调用了refresh()方法来加载配置文件中定义的BeanDefinition,并根据配置文件注册了BeanPostProcessor接口,以及一些配置接口,在加载完bean定义后,还对lazyInit为false的”单例”对象进行了实例化操作. 在这个构造函数返回后,应用程序定义的所有对象以及它们之间的依赖关系就已经在容器中配置好了.
对象间的依赖关系是在调用getBean()方法获取对象时由容器根据定义自动满足的,这部分内容见本文的第二部分.
|
|