Spring的依赖注入容器
依赖注入是Spring框架最核心的能力,Spring框架提供的AOP,WebMVC等其它功能都是以依赖注入容器作为基础构建的,Spring依赖注入容器类似于一个用于组装对象的框架内核,任何应用或者基于Spring的框架都能从中受益
核心概念
理解一个项目,我习惯从它的领域模型开始,领域模型即项目中的核心概念,项目的一切都是围绕其核心概念展开的,Spring的依赖注入容器中有哪些核心概念?
![](https://img.haomeiwen.com/i18730055/a67d6374f9623e3d.png)
bean是spring中的核心概念之一,是spring操作的实体,对于bean,spring中有BeanDefinition接口与之对应,BeanDefinition接口表示bean的定义,它有多个实现类,比如GenericBeanDefinition、AnnotatedGenericBeanDefinition等,每个不同的实现类针对特定的配置方式
BeanFactory是spring的服务域,用于管理bean,BeanFactory也有多个实现类和抽象类,用于实现依赖注入容器的不同的使用方式
ApplicationContext是用于对BeanFactory做增强,提供了BeanFactory的生命周期管理,状态变更广播,注解配置等功能
![](https://img.haomeiwen.com/i18730055/578fd8e55279eeb7.png)
核心类之间的关系
在介绍Spring的框架设计原则之前,我们先通过一个类图来看一下Spring依赖注入容器的核心类之间的关系:
![](https://img.haomeiwen.com/i18730055/6118fe28114536f1.png)
这里是Spring ApplicationContext的继承结构中的几个有代表性的类,由于相差类较多,这里省略了继承关系中间的抽象类,有兴趣的读者可以自己翻阅Spring的源代码,看看其实现细节。ApplicationContext的继承体系使用了模板方法模式,Spring中不同的抽象类为不同类型的ApplicationContext提供了基本的支持,比如AbstractXmlApplicationContext是通过xml配置使用的ApplicationContext的抽象实现。抽象类中实现了ApplicationContext应该具备的共同特性和逻辑相同的方法,具体类中再实现具体的不同点的逻辑,当然,子类中可以覆盖那些抽象类中的方法,以便对ApplicationContext做扩展。
我们先简单的看一下ClassPathXmlApplicationContext类,很多初学者最初就是从ClassPathXmlApplicationContext类开始接触Spring的ApplicationContext对象的,这类是通过加载classpath下的xml配置来初始化Spring的依赖注入容器,并完成bean的装载和依赖对象的注入过程,此类的使用步骤如下:
[if !supportLists]1. [endif]添加Spring的配置文件,比如/META-INF/service.xml
[if !supportLists]2. [endif]在service.xml中配置需要的bean
![](https://img.haomeiwen.com/i18730055/47b6a16c26fe7c88.png)
[if !supportLists]3. [endif]通过service.xml创建ClassPathApplicationContext对象:
![](https://img.haomeiwen.com/i18730055/30c0dd1e5d6fb8e4.png)
前面讲述了常用的ClassPathXmlApplicationContext,现在这里再看一下一个不常用,却更能说明ApplicationContext的结构的类GenericApplicationContext,从上面的继承关系图中可以看出此类实现了BeanDefinitionRegistry接口,BeanDefinitionRegistry接口有什么作用呢?我们在使用Spring的依赖注入容器前,都需要通过XML或者注解的方式向Spring中配置bean,Spring在解析xml或者注解的同时,会将每一个<bean>配置解析成一个BeanDefinition对象注册到spring容器中,BeanDefinitionRegistry接口就是用来做bean的注册的,可以先看一下BeanDefinitionRegistry接口的定义:
![](https://img.haomeiwen.com/i18730055/6f0dfc6a867c5456.png)
可以看到,BeanDefinitionRegistry接口提供了Bean的注册相关的方法,可以用此接口向Spring中注册BeanDefinition对象,移除BeanDefinition对象,获取已注册的BeanDefinition对象等,GenericApplicationContext类没有配置加载功能,无法通过xml配置完成初始化,如果想要使用GenericApplicationContext类,则需要以编码的方式向Spring中注册Bean,例如:
![](https://img.haomeiwen.com/i18730055/c75bc4997d518089.png)
我们再回到前文讲到的ClassPathXmlApplicationContext,ClassPathXmlApplicationContext是GenericApplicationContext的子类,只不过ClassPathXmlApplicationContext通过解析xml的方式来完成对BeanDefinition的注册前面讲到的示例中,在xml中配的<bean name="userService" class="cn.yxffcode.springtest.service.UserServiceImpl"/>配置就类似于以繁琐的编码的方式使用GenericApplicationContext时通过代码创建的BeanDefinition对象,如果我们打开ClassPathXmlApplicationContext及其父类AbstractXmlApplicationContext类,会发现它们有如下结构 (为了简单起见,这里同样省略了中间类,XmlBeanDefinitionReader实际上在AbstractXmlApplicationContext中):
![](https://img.haomeiwen.com/i18730055/6e98c778be65944e.png)
使用ClassPathXmlApplicationContext后,注册Bean的过程由GenericApplicationContext的手动编码变成了的通过xml配置,注册Bean的过程由XmlBeanDefinitionReader负责,配置只是编码方式的一种简化,它并不是框架的核心XmlBeanDefinitionReader的执行过程后文会有详细讲解
框架的初始化
上一节讲过的GenericApplicationContext的使用中,调用了一次refresh方法完成了初始化,同样,ClassPathXmlApplicationContext也需要通过refresh方法完成初始化,只不过默认是自动调用了refresh方法,如果打开ClassPathXmlApplicationContext的源码,我们可以看到多个构造器,其中有一个构造器可以指定是否自动刷新依赖注入容器完成依赖注入窗口的初始化,如果想要手动初始化,我们可以换使用如下方式创建ClassPathXmlApplicationContext对象:
![](https://img.haomeiwen.com/i18730055/73b353d9551c190d.png)
通过IDE的帮助,我们很容易能找到refresh方法的声明,它声明在ApplicationContext的一个子接口ConfigurabeApplicationContext中,我们暂且先不解析此接口的作用,我们可以先看一看refresh方法的实现。
打开AbstractApplicationContext类的refresh方法,这是ApplicationContext的初始化入口,这里先介绍refresh方法的结构:
1. 首先被调用的是prepareRefresh方法,此方法是AbstractApplicationContext的模板方法模式的扩展方法,子类可覆盖此方法,用于在初始化前做前置准备
2. 其次再是获取ConfigurableListableBeanFactory对象,ApplicationContext并不是从头开始做依赖注入容器,它是建立在BeanFactory之上,BeanFactory是Spring实现依赖注入的最核心的接口
3. 第三个步骤是调用prepareBeanFactory,此方法用于向Spring中注册默认的单例bean以及默认的BeanPostProcessor等
4. 第四步是调用postProcessBeanFactory,此方法用于执行准备好了BeanFactory的环境后的后置逻辑,子类可覆盖它实现特定逻辑
5. 第五步是invokeBeanFactoryPostProcessors,从方法名上可以看出,这一步是在调用容器提供的BeanFactoryPostProcessor接口,此接口是Spring提供的扩展接口之一,我们后文再详细讨论
6. 第六步调用registerBeanPostProcessors,此方法用于注册BeanPostProcessor,用于对bean的创建做拦截处理,这一步会创建PostProcessor的对象,BeanPostProcessor接口也是Spring提供的一个扩展接口,后面再详细讨论
7. 第七步是initMessageSource,用于初始化MesasgeSource
8. 第八步是initApplicationEventMulticaster,用于初始化事件广播器,Spring在初始化的前后可以广播ApplicationEvent,用户可自定义ApplicationListener来响应这些事件
9. 第九步调用onRefresh,此方法也是用于扩展,子类中可用它实现其它特殊bean的装配和定制
10. 第十步调用registerListeners,这一步是识别出Spring中配置的ApplicationListener对象
11. 第十一步调用finishBeanFactoryInitialization,创建所有非lazy的单例对象
12. 第十二步finishRefresh,完成初始化,此方法中会触发Lifecycle接口,以及广播事件,触发ApplicationListener
Spring初始化的整个过程非常清晰,整个流程见如下refresh方法的代码:
![](https://img.haomeiwen.com/i18730055/593e30d6257ca2c9.png)
从整个初始化的大方法中,不难发现,ApplicationContext提供了以下BeanFactory没有的能力:
1. BeanFactoryPostProcessor,对BeanFactory做拦截
2. MessageSource,可用于实现消息的转换,国际化等功能
3. 事件广播机制,可对依赖注入民容器的状态做监听
4. 更加可扩展,可定制化
看完了初始化的大方法后,我们再来看看obtainFreshBeanFactory方法,此方法用于获取BeanFactory对象,其实现如下:
![](https://img.haomeiwen.com/i18730055/eed3b6e4a11acee5.png)
这个方法实现非常简洁,其中重要的一步便是refreshBeanFactory方法的调用,此方法是抽象方法,子类可实现以各种不同的方式创建BeanFactory,比如AbstractXmlApplicationContext中通过读取xml配置来创建BeanFactory,而GenericApplicationContext实现类中中则只是创建BeanFactory,没有注册BeanDefinition对象,需要手动编码注册。
RefreshBeanFactory方法的实现之一AbstractRefreshableApplicationContext中的实现是对xml的加载相关的特定逻辑,如下代码:
![](https://img.haomeiwen.com/i18730055/626701b072143ae7.png)
loadBeanDefinitions方法在AbstractRefreshableApplicationContext中是一个抽象方法,用于从xml中加载Bean,AbstractRefreshableApplicationContext中不关注如何加载,其子类的实现类中处理如何加载bean,AbstractXmlApplicationContext中实现了loadBeanDefinitions方法,其实现中通过XmlBeanDefinitionReader加载,而ClassPathXmlApplicationContext类中则实现从类路径下加载xml配置同时FileSystemXmlApplicationContext类则实现了从文件系统中的文件路径下加载xml配置。AbstractXmlApplicationContext中的loadBeanDefinitions方法中使用了XmlBeanDefinitionReader对象,使用方式如下 :
![](https://img.haomeiwen.com/i18730055/3c05396692844307.png)
从上图中可以看到,XmlBeanDefinitionReader中使用到了ResourceLoader接口,此接口定义如下:
![](https://img.haomeiwen.com/i18730055/42f9b369a4ea1890.png)
ResourceLoader接口用于加载资源,Spring的AbstractXmlApplicationContext实现了此接口,用于加载配置。AbstractXmlApplicationContext中的getResource方法子类也可覆盖,比如其中一个子类FileSystemXmlApplicationContext则覆盖了此方法,用于加载文件系统中的xml文件。