1 概述
IoC的意义在于它优雅地解决了类之间的复杂依赖关系,依赖对象的获得方式被反转,对象不必主动去获取被依赖的对象,容器会在适当的时机主动把被依赖对象交到对象手中,通过一种叫做依赖注入的方式。
根据《Spring技术内幕》的介绍,IoC的两个核心接口是BeanFactory和ApplicationContext,它们的操作对象Bean被抽象成BeanDefinition。BeanFactory和ApplicationContext这两个接口都可以视为容器,其中BeanFactory是IoC容器的基本表现形式,ApplicationContext在BeanFactory的基础上提供了更多功能,是IoC容器的高级表现形式。这篇文章主要从接口层面介绍IoC:IoC容器的功能,BeanFactory的启动过程简析。在此基础上我们再通过第二篇和第三篇深入IoC的Bean加载和依赖注入。
1.1 IoC的类体系与功能
1.1.1 IoC接口与功能
a. BeanFactory
BeanFactory只提供Bean获取功能。
b. ListableBeanFactory
提供获取Bean,BeanDefinition,根据注解获取Bean,根据Bean获取注解功能。
c. HieracicalBeanFactory
提供父级BeanFactory获取。
d. ApplicationEventPublisher
事件发布功能。
e. ResourceLoader
classPath:路径下的文件资源获取。
f. ResourcePatterResolver
classPath*:路径下的文件资源获取。
g. EnvironmentCapable
获取环境配置(里面是键值对),其配置主要用于区分应用的运行环境。
h. MessageSource
国际化资源。
i. ApplicationContext
获取容器信息,获取自动注入工厂类。
j. AutoClosable
try-whith-resource必须实现的接口,属于java.io包。多说一句,try-whith-resource是Java的语法糖,它的效果是把 try{打开IO,IO操作}catch(异常){}finally{关闭IO} 这种模式缩短到两步:try(打开/关闭IO){IO操作}finally{不用写关闭IO逻辑} ,甚至一步搞定:try(IO打开/关闭){IO操作} 。
k. LifeCycel
生命周期
l. Closable
关闭IO资源
m. ConfiurableApplicationContext
容器配置和容器生命周期,最重要的是refresh()方法,它是启动容器的地方。
小结:
ApplicationContext接口在BeanFactory基础上增加了资源配置和容器分层功能,ConfigurableApplicationContext接口继承ApplicationContext,收获了Bean获取功能,配置资源获取功能,此外还融入了更多配置和生命周期相关的功能,可谓是接口中的集大成者。
2.开门见山
Spring的启动过程包括三个主要阶段:A.定位资源,B.载入资源,读取Bean信息形成BeanDefinition这种数据结构并保存,C.使用BeanDefinition实例化Bean同时完成依赖注入,根据配置调用初始化方法。B和C阶段是SpringIoC容器参与发挥作用的重要阶段,了解这一节的内容有助于理解后面两篇文章,IoC部分(二)是AB过程的源码解读,IoC部分(三)是C过程的源码解读。
两个术语:
load(载入)——读取Bean配置信息。
register(注册)——把一个东西保存起来,例如保存到Map。
Spring支持Xml方式的配置和Springboot纯注解方式的配置两种形式,无论哪种方式下C过程都是大抵相同的。介绍两种形式下A/B两个过程发生的位置之前,先预览spring容器启动的主要过程,这个过程也是我们下一篇文章的分析重点:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 启动前准备
prepareRefresh();
// 容器初始化,这里是可能载入BeanDefinition的位置
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 容器设置
prepareBeanFactory(beanFactory);
try {
// 再次容器设置,上一步的补充操作
postProcessBeanFactory(beanFactory);
// 调用【BeanFactory后处理器】,处理BeanDefinition,这里可能载入BeanDefinition
invokeBeanFactoryPostProcessors(beanFactory);
// 注册【Bean后处理器】,这些后处理器接收容器事件,有2个在Bean初始化前后自动调用的方法
registerBeanPostProcessors(beanFactory);
// 初始化消息源
initMessageSource();
// 初始化事件广播器
initApplicationEventMulticaster();
// 初始化特殊Bean
onRefresh();
// 注册监听器
registerListeners();
// 完成Bean容器初始化,实例化所有非lazy-init的Bean
finishBeanFactoryInitialization(beanFactory);
// 发布容器启动完成事件
finishRefresh();
}
2.1 Xml方式
具体容器类型:XmlWebApplicationContext
a.初始化容器:
XMLBeanDefinition读取器:XmlBeanDefinitionReader(读取IO流)
子读取器:DefaultBeanDefinitionDocumentReader
解析过程:解析Xml节点,根据xml标签namespace读取xml节点中的信息,使用
内部注解后处理器(org.springframework.context.annotation.internalConfigurationAnnotationProcessor)
MyBatis后处理器(org.mybatis.spring.mapper.MapperScannerConfigurer)
c.注册:DefaultListableBeanFactory
2.2 纯注解方式(SpringBoot)
具体容器类型:AnnotationConfigServletWebServerApplicationContext(默认)
//SpringBoot启动类SpringApplication中的方法,根据配置创建ApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET: //AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE: //AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default: //AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
a.初始化容器:
无操作
b.调用后处理器:
后处理器:ConfigurationClassPostProcessor
解析器:ConfigurationClassParser
子解析器:ComponentScanAnnotationParser
c.注册:DefaultListableBeanFactory
小结:不同的容器,载入和解析BeanDefinition的方式有很大差别,但是这些差别都被接口方法屏蔽,要么在obtainFreshBeanFactory()中,要么在invokeBeanFactoryPostProcessors()中,大的流程在抽象中成型,细的实现在具体中完善,高层不依赖于底层细节,细节连贯起来是高层的体现。学习源码的作用不仅是搞清楚原理,更是对设计思想的融会贯通。
3.调试技巧
这一节交代调试的方式,下一节跟着调试代码的过程细细品味实现细节。
工具:Intrlij IDEA
说明:下面的断点行号均为源码中的行号,不是jar反编译代码的行号
a.断点位置
Spring容器启动位置:org.springframework.context.support.AbstractApplicationContext 515,549
BeanDefinition注册位置:org.springframework.beans.factory.support.DefaultListableBeanFactory 918,924,938
b.设置断点程序挂起条件
BeanDefinition注册位置会有很多BeanDefinition被put到Map中,通常我们只关心某个特定类的beanDefinition是什么时候放进去的,这时可以设置断点程序挂起条件:在断点位置右键编辑挂起条件后点击"Done",开始调试。
c.查看类继承体系
在类名上右键>Diagrams>showDiagram,就会生文章开始的那副图片,一张排列完好的类继承结构图,我们甚至可以右键输出成各种格式的图片保存起来。