Spring-Framework 容器启动流程

剖析Spring-Framework IoC容器的初始化过程。

Spring-Framework体系结构

在介绍Spring-Framework容器启动之前,还是需要先了解下Spring的体系结构,这样我们能知道Spring具体是做了什么工作,便于对全局有所把握,否则可能会无限陷入细节中。

从官网中截了张Spring体系图,可以看出Spring主要分为下面的几个部分:

  • Core Container
  • AOP/Apsect
  • Data Access
  • Web
  • Instrumentation/Message/Test等其他

从图中可以看出最底层是Test和Core Container模块。
这两个模块是Spring-Framework的基石、

Test模块提供基于JUnit/TestNG功能的Spring组件测试。

Core Container由spring-beans,spring-core,spring-context,spring-expression组成。

分别提供Bean装配,IOC容器,容器上下文,Spring表达式等功能。

在基于core container的基础上,提供AOP,Aspect,Data Access,Web等功能。

下面我挑几个主要模块介绍下。

spring-core

spring-core是最底层的模块,也是最多被其他模块依赖的。

它内部提供了对Java特性的封装,比如反射,IO,ASM,cglib之类的。

同时对资源获取进行了封装,提供ResourceLoader,Resource这样的接口,对资源获取进行管理。

这些特性被spring-beans/spring-aop模块引用,可以理解spring-core模块主要是工具类。

spring-beans

提供BeanFactoryBeanDefinition等概念,是spring作为IoC容器关键模块。可以理解为spring对bean的管理主要在这里完成。

spring-context

提供ApplicationContextApplicationEvent等接口。

这个模块整合了spring-beans和spring-aop,作用就是提供spring运行所必需的context,比如bean装配,aop,context 事件发送等。

我们今天要分析的容器启动工作主要就是在spring-context中完成的。

Spring容器关键接口设计

本文的容器接口主要是围绕Ioc部分进行,至于Aop和其他的暂时就不在这里讨论啦,要不然篇幅太长,所以后面有时间单独介绍。

先看一个简单的xml容器使用,与我们常见spring-boot启动容器原理是一致的,只不过bean的定义一个使用xml,一个使用java config。

容器启动的入口是ApplicationContext,这里指定的具体类型是ClassPathXmlApplication。

我们来根据上面的代码看下涉及到Ioc的容器关键接口定义。

下面对关键接口逐一介绍下。

BeanFactory

BeanFactory的职责是管理容器中所有的Bean,并且提供一些获取Bean的方法。

我们也可以直接把BeanFactory作为容器使用,比如下面的代码。

当然了,这么使用会比较麻烦,比如我们得需要使用代码构造BeanDefinition,这里就会有很多不方便的地方,Spring在spring-context层为我们做了许多的封装。

一般情况下我们是使用注解或者xml声明bean的,那么spring-context会自动帮我们把xml或者注解转为BeanDefinition,注册进BeanDefinitionRegistry(其实是BeanFactory)。

另外,从上面的类图上看,ApplicationContext是实现了BeanFactory接口的,这里只是提供一个方便获取bean的方法。实际实现仍然是构造了一个DefaultListableBeanFactory实例。

总结下:

bean管理这里涉及到的角色主要有:

  • BeanFactory: 管理/获取Bean
  • BeanDefinition: 使用名字/id/类型注册Bean
  • BeanDefinitionRegistry:管理BeanDefinition
  • DefaultListableBeanFactory: BeanFactory和BeanDefinitionRegistry的实现类,也是context使用的默认实现。

ApplicationContext

ApplicationContext是spring容器的核心接口,我们看下它的继承关系就可以看出它的职责。

继承关系也不复杂,这里我直接使用idea的uml功能截取了。

根据uml图总结下,主要提供的功能有:

  1. 实现BeanFactory等一系列bean操作接口,提供bean的管理/获取能力。
  2. 实现EnvironmentCapable接口,提供获取Environment能力。
  3. 实现ApplicaitonEventPubisher接口,会主动向订阅者发布容器状态切换事件。比如ContextRefreshEvent。
  4. 实现ResourceLoader接口,提供通用的资源访问操作。

ResourceLoader

ResourceLoader这个概念类似于Java里的URI,将多种类型的资源用一个协议表示。

ApplicationContext中会有个默认的DefaultResourceLoader。spring容器的各种资源会统一使用这个ResourceLoader进行加载,并返回一个Resource实例表示资源。

使用ResourceLoader加载资源的代码示例如下。

spring-framework启动流程

现在终于进入正题,我们来剖析下容器启动的IoC部分,也就是说Bean是如何从声明的xml装载进入Spring容器的。

还是这个代码:

xml部分:

beans节点下面是XML的DTD,需要写上,否则这些标签无法识别。

使用bean标签进行定义,属性使用property进行描述。

当然了,这些只是xml的用法,现在使用更为广泛的是使用pojo进行定义,比如加上@Component注解。

不过原理都是一样的,他们都会被解析成BeanDefinition注册进入BeanFactory。

只不过解析的方式不同,xml会在io读取后解析。

注解会通过遍历classpath扫描到后解析。

我们看下构造器中会调用refresh方法,refresh方法会初始化BeanFactory,使我们要分析的核心逻辑。

refresh实现

refresh方法具体实现在AbstractApplicationContext中。

核心代码如下:

obtainFreshBeanFactory实现

obtainFreshBeanFactory是refresh中非常核心的一个方法,它的作用是新创建一个BeanFactory,并将BeanDefinition注册进入这个BeanFactory中。

我们一起分析下,obtainFreshBeanFactory实现在AbstractApplicationContext中。

第一行调用到了refreshBeanFactory,这个实现在AbstractRefreshableApplicationContext

可以看到,首先判断如果当前已经有一个BeanFactory了,那么就需要先销毁。

然后构造一个DefaultListableBeanFactory使用。

最后在赋值到成员变量前,调用了loadBeanDefinitions
这个方法是抽象的模板方法,子类根据加载bean的方式不同有各自的实现。

这里因为我们指定的是classPath下的xml,所以这个实现在AbstractXmlApplicationContext中。

之后的代码就是各种在xml reader的细枝末节了,我们不过多分析,理解主要脉络即可。

如果大家有兴趣的话,可以自己看下,最后生成BeanDefinition的时机在DefaultBeanDefinitionDocumentReaderprocessBeanDefinition方法中。

回到上面refresh的调用处,下一个关键是使用finishBeanFactoryInitialization预加载所有非lazy-init的bean。

finishBeanFactoryInitialization

我们看下实现:

核心逻辑就一个调用到beanFactory.preInstantiateSingletons(),预加载所有的非lazy-init的单例bean。

可以看到,上面的逻辑就是遍历所有的bean,如果满足单例并且无懒加载的话,就直接初始化了。

其实还有一个关键逻辑getBean,这个操作非常复杂,鉴于篇幅我们后面单独出文章介绍吧。

最后,可以看出refresh方法结束后,所有单例bean都已经在容器中完成初始化了,我们再用一张流程图总结下这个过程。

参考

spring-framework官方文档

发表评论

邮箱地址不会被公开。 必填项已用*标注