Spring Boot 核心知识

在过去两三年的Spring生态圈,最让人兴奋的莫过于Spring Boot框架。或许从命名上就能看出这个框架的设计初衷:快速的启动Spring应用。因而Spring Boot应用本质上就是一个基于Spring框架的应用,它是Spring对“定优先于配置”理念的最佳实践产物,它能够帮助开发者更快速高效地构建基于Spring生态圈的应用。

那Spring Boot有何魔法?

自动配置起步依赖Actuator命令行界面(CLI) 是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可选特性,虽然它功能强大,但也引入了一套不太常规的开发模型,因而这个系列的文章仅关注其它3种特性。如文章标题,本文是这个系列的第一部分,将为你打开Spring Boot的大门,重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分核心内容,理解一些Spring框架的基础知识,将会让你事半功倍。

一、抛砖引玉:探索Spring IoC容器

如果有看过 SpringApplication.run()方法的源码,Spring Boot冗长无比的启动流程一定会让你抓狂,透过现象看本质,SpringApplication只是将一个典型的Spring应用的启动流程进行了扩展,因此,透彻理解Spring容器是打开Spring Boot大门的一把钥匙。

1.1、Spring IoC容器

可以把Spring IoC容器比作一间餐馆,当你来到餐馆,通常会直接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做出来?可能你根本就不关心。IoC容器也是一样,你只需要告诉它需要某个bean,它就把对应的实例(instance)扔给你,至于这个bean是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。

作为餐馆,想要做出菜肴,得知道菜的原料和菜谱,同样地,IoC容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。BeanDefinition对象就承担了这个责任:容器中的每一个bean都会有一个对应的BeanDefinition实例,该实例负责保存bean对象的所有必要信息,包括bean对象的class类型、是否是抽象类、构造方法和参数、其它属性等等。当客户端向容器请求相应对象时,容器就会通过这些信息为客户端返回一个完整可用的bean实例。

原材料已经准备好(把BeanDefinition看着原料),开始做菜吧,等等,你还需要一份菜谱, BeanDefinitionRegistryBeanFactory就是这份菜谱,BeanDefinitionRegistry抽象出bean的注册逻辑,而BeanFactory则抽象出了bean的管理逻辑,而各个BeanFactory的实现类就具体承担了bean的注册以及管理工作。它们之间的关系就如下图:

Spring Boot 核心知识

BeanFactory、BeanDefinitionRegistry关系图(来自:Spring揭秘)

DefaultListableBeanFactory作为一个比较通用的BeanFactory实现,它同时也实现了BeanDefinitionRegistry接口,因此它就承担了Bean的注册管理工作。从图中也可以看出,BeanFactory接口中主要包含getBean、containBean、getType、getAliases等管理bean的方法,而BeanDefinitionRegistry接口则包含registerBeanDefinition、removeBeanDefinition、getBeanDefinition等注册管理BeanDefinition的方法。

下面通过一段简单的代码来模拟BeanFactory底层是如何工作的:

 
  1.  

    // 默认容器实现

     

  2.  

    DefaultListableBeanFactory beanRegistry = newDefaultListableBeanFactory();

     

  3.  

    // 根据业务对象构造相应的BeanDefinition

     

  4.  

    AbstractBeanDefinition definition = newRootBeanDefinition(Business.class,true);

     

  5.  

    // 将bean定义注册到容器中

     

  6.  

    beanRegistry.registerBeanDefinition("beanName",definition);

     

  7.  

    // 如果有多个bean,还可以指定各个bean之间的依赖关系

     

  8.  

    // ........

     

  9.  

  10.  

    // 然后可以从容器中获取这个bean的实例

     

  11.  

    // 注意:这里的beanRegistry其实实现了BeanFactory接口,所以可以强转,

     

  12.  

    // 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的

     

  13.  

    BeanFactory container = (BeanFactory)beanRegistry;

     

  14.  

    Business business = (Business)container.getBean("beanName");

     

这段代码仅为了说明BeanFactory底层的大致工作流程,实际情况会更加复杂,比如bean之间的依赖关系可能定义在外部配置文件(XML/Properties)中、也可能是注解方式。Spring IoC容器的整个工作流程大致可以分为两个阶段:

①、容器启动阶段

容器启动时,会通过某种途径加载 ConfigurationMetaData。除了代码方式比较直接外,在大部分情况下,容器需要依赖某些工具类,比如:BeanDefinitionReader,BeanDefinitionReader会对加载的 ConfigurationMetaData进行解析和分析,并将分析后的信息组装为相应的BeanDefinition,最后把这些保存了bean定义的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器的启动工作就完成了。这个阶段主要完成一些准备性工作,更侧重于bean对象管理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段完成。

来看一个简单的例子吧,过往,所有的bean都定义在XML配置文件中,下面的代码将模拟BeanFactory如何从配置文件中加载bean的定义以及依赖关系:

 
  1.  

    // 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例

     

  2.  

    BeanDefinitionRegistry beanRegistry = newDefaultListableBeanFactory();

     

  3.  

    // XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件

     

  4.  

    XmlBeanDefinitionReader beanDefinitionReader = newXmlBeanDefinitionReaderImpl(beanRegistry);

     

  5.  

    // 加载配置文件

     

  6.  

    beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");

     

  7.  

  8.  

    // 从容器中获取bean实例

     

  9.  

    BeanFactory container = (BeanFactory)beanRegistry;

     

  10.  

    Business business = (Business)container.getBean("beanName");

     

②、Bean的实例化阶段

经过第一阶段,所有bean定义都通过BeanDefinition的方式注册到BeanDefinitionRegistry中,当某个请求通过容器的getBean方法请求某个对象,或者因为依赖关系容器需要隐式的调用getBean时,就会触发第二阶段的活动:容器会首先检查所请求的对象之前是否已经实例化完成。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。当该对象装配完毕后,容器会立即将其返回给请求方法使用。

BeanFactory只是Spring IoC容器的一种实现,如果没有特殊指定,它采用采用延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进行初始化和依赖注入操作。而在实际场景下,我们更多的使用另外一种类型的容器:ApplicationContext,它构建在BeanFactory之上,属于更高级的容器,除了具有BeanFactory的所有能力之外,还提供对事件监听机制以及国际化的支持等。它管理的bean,在容器启动时全部完成初始化和依赖注入操作。