在过去两三年的Spring生态圈中,最让人兴奋的莫过于Spring Boot框架。或许从命名上就能够看出这个框架的设计初衷:快速启动Spring应用。因而Spring Boot应用本质上就是一个基于Spring框架的应用,它是Spring对“约定优于配置”理念的最佳实践产物,它能够帮助开发者更快速高效地构建基于Spring生态圈的应用。
关于SpringBoot有哪些特性,SpringBoot官网是这么描述的:
Features
-
Create stand-alone Spring applications
-
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
-
Provide opinionated 'starter' dependencies to simplify your build configuration
-
Automatically configure Spring and 3rd party libraries whenever possible
-
Provide production-ready features such as metrics, health checks and externalized configuration
-
Absolutely no code generation and no requirement for XML configuration
它的意思是说,SpringBoot可以创建独立的Spring应用程序,并且有内置的服务器,同时可以大量的减少构建配置,基本上不需要代码的生成和XML的配置以及尽可能多的提供第三方库。对于我们开发人员来说最明显的优势就是,我们现在搭建一个项目不需要像以前搭建传统框架一样需要大半天的时间写各种XML配置,而SpringBoot只需要你通过注解的形式配置开发当中一些动态的参数即可,比如数据库的连接信息。
那Spring Boot有何魔法?自动配置,起步依赖,Actuator,命令行界面(CLI)是Spring Boot最重要的4大核心特性,其中CLI是Spring Boot的可选特性,虽然它功能强大,但也引入了一套不太常规的开发模型。
本文将对你打开Spring Boot的大门,重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分的核心内容,就要理解Spring框架的基础知识,将会让你事半功倍。
探索Spring IoC容器
如果有看过 SpringApplication.run()
方法的源码,Spring Boot 冗长无比的启动流程一定会让你抓狂,透过现象看本质,SpringApplication
只是将一个典型的 Spring 应用的启动流程进行了扩展,因此,透彻理解 Spring 容器是打开 Spring Boot 大门的一把钥匙。
Spring IoC容器
可以把 Spring IoC
容器比作一间餐馆,当你来到餐馆,通常会直接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做出来?可能你根本就不关心。IoC 容器也是一样,你只需要告诉它需要某个bean,它就把对应的实例(instance)扔给你,至于这个bean是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。
作为餐馆,想要做出菜肴,得知道菜的原料和菜谱,同样地,IoC 容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。 BeanDefinition
对象就承担了这个责任:容器中的每一个 bean 都会有一个对应的 BeanDefinition
实例,该实例负责保存bean对象的所有必要信息,包括 bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。当客户端向容器请求相应对象时,容器就会通过这些信息为客户端返回一个完整可用的 bean 实例。
原材料已经准备好(把 BeanDefinition 看着原料),开始做菜吧,等等,你还需要一份菜谱, BeanDefinitionRegistry
和 BeanFactory
就是这份菜谱,BeanDefinitionRegistry
抽象出 bean
的注册逻辑,而 BeanFactory
则抽象出了 bean
的管理逻辑,而各个 BeanFactory
的实现类就具体承担了 bean 的注册以及管理工作。
①、容器启动阶段
容器启动时,会通过某种途径加载 ConfigurationMetaData
。除了代码方式比较直接外,在大部分情况下,容器需要依赖某些工具类,比如: BeanDefinitionReader
,BeanDefinitionReader
会对加载的 ConfigurationMetaData
进行解析和分析,并将分析后的信息组装为相应的 BeanDefinition
,最后把这些保存了 bean
定义的 BeanDefinition
,注册到相应的 BeanDefinitionRegistry
,这样容器的启动工作就完成了。这个阶段主要完成一些准备性工作,更侧重于 bean
对象管理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段完成。
来看一个简单的例子吧,过往,所有的 bean
都定义在 XML
配置文件中,下面的代码将模拟 BeanFactory
如何从配置文件中加载 bean
的定义以及依赖关系:
1 // 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例 2 BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory(); 3 // XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件 4 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(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
,在容器启动时全部完成初始化和依赖注入操作。
Spring容器扩展机制
IoC 容器负责管理容器中所有bean的生命周期,而在 bean
生命周期的不同阶段,Spring 提供了不同的扩展点来改变 bean
的命运。在容器的启动阶段, BeanFactoryPostProcessor
允许我们在容器实例化相应对象之前,对注册到容器的 BeanDefinition
所保存的信息做一些额外的操作,比如修改 bean 定义的某些属性或者增加其他信息等。
如果要自定义扩展类,通常需要实现 org.springframework.beans.factory.config.BeanFactoryPostProcessor
接口,与此同时,因为容器中可能有多个BeanFactoryPostProcessor
,可能还需要实现 org.springframework.core.Ordered
接口,以保证BeanFactoryPostProcessor
按照顺序执行。Spring提供了为数不多的BeanFactoryPostProcessor
实现,我们以 PropertyPlaceholderConfigurer
来说明其大致的工作流程。
在Spring项目的XML配置文件中,经常可以看到许多配置项的值使用占位符,而将占位符所代表的值单独配置到独立的properties文件,这样可以将散落在不同XML文件中的配置集中管理,而且也方便运维根据不同的环境进行配置不同的值。这个非常实用的功能就是由PropertyPlaceholderConfigurer
负责实现的。
根据前文,当BeanFactory在第一阶段加载完所有配置信息时,BeanFactory中保存的对象的属性还是以占位符方式存在的,比如 ${jdbc.mysql.url}
。当PropertyPlaceholderConfigurer
作为BeanFactoryPostProcessor
被应用时,它会使用properties配置文件中的值来替换相应的BeanDefinition
中占位符所表示的属性值。当需要实例化bean时,bean定义中的属性值就已经被替换成我们配置的值。当然其实现比上面描述的要复杂一些,这里仅说明其大致工作原理,更详细的实现可以参考其源码。
与之相似的,还有 BeanPostProcessor
,其存在于对象实例化阶段。跟BeanFactoryPostProcessor
类似,它会处理容器内所有符合条件并且已经实例化后的对象。简单的对比,BeanFactoryPostProcessor
处理bean的定义,而BeanPostProcessor
则处理bean完成实例化后的对象。BeanPostProcessor
定义了两个接口:
1 public interface BeanPostProcessor { 2 // 前置处理 3 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; 4 // 后置处理 5 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; 6 }