Spring Boot启动
Spring Boot是Spring旗下的一个子项目,其设计目的是简化Spring应用的初始搭建及开发过程,Spring Boot可以快速启动和运行你的Spring应用服务。
Spring Boot概述
Spring Boot本质上是基于Spring内核的一个快速开发框架,是“约定优先于配置”理念下的最佳实践,通过解析Spring Boot的启动过程,可以帮助我们逐渐了解它的工作机制和其背后整合Spring快速开发的实现原理。
磨刀不误砍柴工
在开始讲解Spring Boot之前,首先让我们从整体架构上认识Spring家族,正所谓“不知全局者不足以谋一域”,如下图所示是Spring Boot与Spring生态的关系。
Spring Core:Spring Core是Spring框架的核心模块,集成在Spring框架中,提供了我们熟知的控制反转机制。
Spring的核心是管理轻量级的JavaBean组件,提供对组件的统一生命周期和配置组装服务管理,如下图所示。
Spring框架:Spring框架的核心就是,控制反转和面向切面(AOP)机制,同时它为开发者了提供众多组件,包括Web容器 组 件 ( Spring Web MVC ) 、 数 据 接 入 组 件 ( SpringDAO)、数据对象映射组件(Spring ORM)等。这些组件基于Spring Core的IoC容器开发,同时Spring框架可以配置管理所有轻量级的JavaBean组件和维护众多JavaBean组件之间的关系。简单地说,Spring为开发者提供了一个一站式的轻量级开发框架平台。
- Spring Boot:Spring Boot是一个微服务框架,以“Boot”命名,很好地说明这个框架的初衷——快速启动。Spring Boot从代码结构上来说包含了Spring框架,或者说是在Spring框架基础上做的一个扩展。它在延续Spring框架的核心思想和设计理念的基础上,简化了应用的开发和组件的集成难度。
Spring Boot是为了简化Spring应用的创建、运行、调试、部署等特性而出现的,使用Spring Boot脚手架可以让微服务开发者做到专注于业务领域的开发,无须过多地关注底层技术实现细节。
- Spring中的IoC机制与JavaConfig的关系:我们知道,SpringIoC 机 制 是 Spring 框 架 的 核 心 , 通 过 控 制 反 转 机 制 实 现JavaBean组件和JavaBean组件依赖关系的管理。如果不使用IoC技术,开发者需要手动创建、查找、管理业务逻辑对象和依赖。
如果说“程序=算法+数据”,那么这里我们可以把这些JavaBean组件看作我们需要维护的数据,当数据(对象)规模膨胀时,将给我们的应用带来极大的耦合度和复杂度。而通过Spring IoC容器可以方便地管理我们的对象。
下图是Spring IoC容器给开发人员带来的编程模型的转变,它可以降低程序代码之间的耦合度,将耦合的对象依赖关系从代码中移除,通过将对象和依赖关系放在注解(或者XML配置文件)中,将程序对组件的控制权转交给IoC容器,进行统一管理。开发者只需要专注于业务的JavaBean组件的实现,查找逻辑和依赖逻辑全部由Spring IoC容器帮助打理。
在Spring 3.0之前,JavaBean组件一直是通过XML配置文件来配置管理的,Spring 3.0之后为我们提供了Java代码形式(JavaConfig)的配置功能。JavaConfig功能从Spring 3.0以后已经包含在了Spring的 核 心 模 块 中 ( JavaConfig 并 非 Spring Boot 新 特 性 ) , 可 以 说JaveConfig就是Spring IoC容器的纯Java实现版本。在Spring Boot中,JavaConfig已经完全代替applicationContext.xml,实现了XML的零配置,如下所示是两种不同配置模式示例。
- 基于XML配置文件方式
基于JavaConfig方式
JavaConfig可以被看成一个XML文件,只不过是用Java代码编写的。
JavaConfig的优势
上面我们介绍了Spring实现的JavaBean管理模式,主要有XML配置文件和JavaConfig两种方式,Spring Boot采用的JavaConfig主要有下面几个优点。
- 面向对象配置:由于配置被定义在JavaConfig中的类中,可以充分使用Java面向对象的功能,用户可以实现配置继承、配置重写等面向对象特性。
- 减少大量滥用XML:由于Spring把所有逻辑业务类都以XML配置文件的形式来表达Bean,造成XML文件充斥整个项目,带来了开发、维护的复杂性,开发人员需要频繁地在XML和Java语言之间来回切换。
- 类 型 安 全 和 重 构 支 持 : 因 为 注 释 在 类 源 代 码 中 , 所 以JavaConfig为应用提供了类型安全的方法来配置管理Spring容器,由于Java对泛型的支持,我们可以按照类型而不是名称检索JavaBean,这带来了更大的灵活性和重构支持。
本节我们带大家简单地回顾了Spring框架的整体架构,这些都是“开胃菜”,下面让我们看看Spring Boot是如何创建一个独立运行、生产级别的Spring应用的。
Spring Boot快速搭建
1.开发环境准备工作
在开始之前,我们需要搭建IDE(集成开发环境),目前流行的IDE有IntelliJ IDEA、Spring Tools、Visual Studio Code和Eclipse等 。 Java Development Kit ( JDK ) 我 们 推 荐 使 用 OpenJDK 8 或 者OpenJDK 11。
2.使用Spring-Initializr快速构建工程
我们可以通过Spring官方提供的Spring Initializr来构建Spring Boot项目,它不仅完美支持IDEA和Eclipse,而且能自动生成启动类和单元测试代码,给开发人员带来了极大的便利。如下图所示是Spring官方的Spring Boot构建工程模板,你可以使用Maven或者Gradle进行初始化项目的构建。你需要填写Group、Artifact等工程元数据信息,最后单击GENERATE按钮生成Spring Boot模板工程。
3.静态工程模板
Spring Boot静态工程目录模板示例如下:
4.增加启动代码,开始Spring Boot的启动流程
5.运行Spring Boot应用
@SpringBootApplication注解详解
下面我们通过源码了解Spring Boot是如何工作的。首先我们看一下@SpringBootApplication注解,它是用来标注主程序的,表明这是一个Spring Boot应用,也是一个组合注解,主要由下面三个注解组成:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
@SpringBootApplication注解定义如下:
@SpringBootConfiguration注解解析
@SpringBootConfiguration注解代码如下:
@SpringBootConfiguration来源于@Configuration,二者的功能都是将当前类标注为配置类,并将类中以@Bean注解标记的方法的实例注入Spring容器。@Configuration是JavaConfig形式的Spring容器的配置类所使用的。
@EnableAutoConfiguration注解解析
@EnableAutoConfiguration注解代码如下:
@EnableAutoConfiguration可以说是Spring Boot启动注解的主角,其中最关键的注解是@Import(
AutoConfigurationImportSelector.class)。
借助
AutoConfigurationImportSelector类,注解@EnableAutoConfiguration可以帮助Spring Boot应用将所有符合条件的@Configuration配置加载到当前Spring Boot创建并使用的Spring容器中。
注意:在SpringBoot1.5以后,
EnableAutoConfigurationImportSelector类已经被AutoConfigurationImportSelector类所取代。
下 面 我 们 看 一 下
AutoConfigurationImportSelector 类 中 的selectImports方法是如何实现自动加载配置类的,源码如下:
getCandidateConfigurations方法如下:
getCandidateConfigurations方法的主要逻辑是调用Spring框架提供的一个工具类Spring-FactoriesLoader。SpringFactoriesLoader是Spring内部提供的一种约定俗成的加载配置类的方式,使用SpringFactoriesLoader 可 以 从 指 定 classpath 下 读 取 META�INF/spring.factories文件的配置,并返回一个字符串数组。通过这个方法,所有自动配置类都会自动加载到Spring容器中。
如下图所示是一个Spring Boot应用启动过程的内存快照,可以看到,在“配置列表对象”中除了Spring自带的配置类,还有第三方的自动配置类。我们可以根据SpringFactoriesLoader规定的协议自定义配置类。
上面框线标注的配置类对应下面的META-INF/spring.factories配置文件,这个Properties格式的文件中主键(Key)可以是接口、注解、抽象类的全名,值(Value)是以“,”分割的实现类,如下所示:
总 结 一 下 , @EnableAutoConfiguration 的 作 用 及SpringFactoriesLoader启动加载配置类流程如下:
(1)从classpath中搜索所有META-INF/spring.factories配置文件,然后将其中
org.springframework.boot.autoconfigure.EnableAutoConfiguration的Key对应的配置项加载到Spring容器。
(2)@EnableAutoConfiguration可以排除配置选项,排除方式有两 种 , 一 种 方 式 是 在 使 用 @SpringBootApplication 注 解 时 , 使 用exclude属性排除指定的类,代码如下:
另外一种方式是:单独使用@EnableAutoConfiguration注解,其内部的关键代码实现如下:
@ComponentScan注解解析
@ComponentScan注解代码如下:
@ComponentScan注解本身是Spring框架加载Bean的主要组件,它并不是Spring Boot的新功能,这里不对@ComponentScan扫描和解析Bean的过程进行详细说明,感兴趣的读者可以自行查阅资料进行了解。
@ComponentScan注解的作用总结一句话就是:定义扫描路径,默认会扫描该类所在的包下所有符合条件的组件和Bean定义,最终将这些Bean加载到Spring容器中。下面是我们总结的@ComponentScan的主要使用方式:
- @ComponentScan注解默认会装配标识了@Component注解的类到Spring容器中。
- 通过basepackage可以指定扫描包的路径。
- 通过includeFilters将扫描路径下没有以上注解的类加入Spring容器。
- 通过excludeFilters过滤出不用加入Spring容器的类。
Spring Boot启动流程进阶
每一个Spring Boot程序都有一个主入口,这个主入口就是main方法,而main方法中都会调用SpringBootApplication.run方法,一个快速了解SpringBootApplication启动过程的好方法就是在run方法中打一个断点,然后通过Debug的模式启动工程,逐步跟踪了解SpringBoot源码是如何完成环境准备和启动加载Bean的。
查看SpringBootApplication.run方法的源码就可以发现SpringBoot的启动流程主要分为两个大的阶段:初始化SpringApplication和运行SpringApplication。而运行SringApplication的过程又可以细化为下面几个部分,后面我们会对启动的主要模块加以详解。
初始化SpringApplication
步骤1进行SpringApplication的初始化,配置基本的环境变量、资 源 、 构 造 器 、 监 听 器 。 初 始 化 阶 段 的 主 要 作 用 是 为 运 行SpringApplication对象实例启动做环境变量准备以及进行必要资源构造器的初始化动作,代码如下:
SpringApplication构造方法的核心是this.initialize(sources)初始化方法,SpringApplication通过调用该方法完成初始化工作。deduceWebEnvironment方法用来判断当前应用的环境,该方法通过获取两个类来判断当前环境是否是Web环境。
而
getSpringFactoriesInstances方法主要用来从spring.factories文件中找出Key为ApplicationContextInitializer的类并实例化,然后调用setInitializers方法设置到SpringApplication的initializers属性中,找到它所有应用的初始化器。接着调用setListeners方法设置应用监听器,这个过程可以找到所有应用程序的监听器,最后找到应用启动主类名称。
运行SpringApplication
步骤2 Spring Boot正式地启动加载过程,包括启动流程监控模块、配置环境加载模块、ApplicationContext容器上下文环境加载模块。refreshContext方法刷新应用上下文并进行自动化配置模块加载,也就是上文提到的SpringFactoriesLoader根据指定classpath加载META-INF/spring.factories文件的配置,实现自动配置核心功能。
运行SpringApplication的主要代码如下:
1.SpringApplicationRunListeners应用启动监控模块
应用启动监控模块对应上述步骤2.1,它创建了应用的监听器
SpringApplicationRunListeners 并 开 始 监 听 , 监 听 模 块 通 过 调 用getSpringFactoriesInstances私有协议从META�INF/spring.factories文件中取得SpringApplicationRunListeners监听器实例。
当前的事件监听器
SpringApplicationRunListeners中只有一个EventPublishingRunListener广播事件监听器,它的Starting方法会封装成SpringApplicationEvent事件广播出去,被SpringApplication中配置的listeners所监听。这一步骤执行完成后也会同时通知SpringBoot其他模块目前监听初始化已经完成,可以开始执行启动方案了。
2.ConfigurableEnvironment配置环境模块和监听
对应上述步骤2.2,下面是分解步骤说明。
(1)创建配置环境,对应上述步骤2.2.1,创建应用程序的环境信息。如果是Web程序,创建
StandardServletEnvironment,否则创建StandardEnvironment。
(2)加载属性配置文件,对应上述步骤2.2.2,将配置环境加入监 听 器 对 象 中 (
SpringApplicationRunListeners ) 。 通 过configurePropertySources方法设置properties配置文件,通过执行configureProfiles方法设置profiles。
(3)配置监听,对应上述步骤2.2.3,发布environmentPrepared事件,即调用ApplicationListener的onApplicationEvent事件,通知Spring Boot应用的environment已经准备完成。
3.ConfigurableApplicationContext配置应用上下文
对应上述步骤2.3,下面是分解步骤说明。
(1)配置Spring应用容器上下文对象,对应上述步骤2.3.1,它的作用是创建run方法的返回对象
ConfigurableApplicationContext(应用配置上下文),此类主要继承 了 ApplicationContext 、 Lifecycle 、 Closeable 接 口 , 而ApplicationContex是Spring框架中负责Bean注入容器的主要载体,负责Bean加载、配置管理、维护Bean之间的依赖关系及Bean的生命周期管理。
(2)配置基本属性,对应上述步骤2.3.2,prepareContext方法将listeners、environment、banner、applicationArguments等重要组件与Spring容器上下文对象相关联。借助SpringFactoriesLoader查找可用的
ApplicationContextInitializer,它的initialize方法会对创 建 好 的 ApplicationContext 进 行 初 始 化 , 然 后 它 会 调 用SpringApplicationRunListener的contextPrepared方法,此时SpringBoot应用的ApplcaionContext已经准备就绪,为刷新应用上下文准备好容器。
( 3 ) 刷 新 应 用 上 下 文 , 对 应 上 述 的 步 骤 2.3.3 ,refreshContext(context)方法将通过工厂模式产生应用上下文环境中所需要的Bean。实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载、Bean的实例化等核心工作。最后
SpringApplicationRunListener调用finished方法告诉Spring Boot应用程序容器已经完成ApplicationContext装载。
Spring Boot自动装配机制
Spring Boot的快速发展壮大,得益于“约定优于配置”的理念。
Spring Boot 自 动 装 配 流 程 中 最 核 心 的 注 解 是@EnableAutoConfiguration,在上一节的启动流程中我们已经讲过,它 可 以 借 助 SpringFactoriesLoader“ 私 有 协 议 特 性 ” 将 标 注 了@Configuration的JavaConfig全部加载到Spring容器中,而如果是基于条件的装配及调整顺序的Bean装配,需要Spring Boot有额外的自动化装配机制。下面从@EnableAutoConfiguration开始进阶讲解,加深我们对Spring Boot自动装配机制的认识。
基于条件的自动装配
下面是@EnableAutoConfiguration注解,它同样是一个组合注解:
从源码可见,最关键的就是@Import(
AutoConfigurationImportSelector.class)注解的实现。借助EnableAutoConfigurationImportSelector模块,@EnableAutoConfiguration可以帮助Spring Boot应用将所有符合条件的@Configuration配置都加载到当前的容器中。同时借助Spring框架原有的底层工具SpringFactoriesLoader(服务发现机制)和根据特定条件装备Bean的Conditionxxx条件注解实现智能的自动化配置工作。
Bean的加载过滤过程主要是通过下面的方法实现的。
源码解析如下:
(1)执行
AutoConfigurationMetadataLoader.loadMetadata ( this.beanClassLoader)会加载META-INF/spring-autoconfigure�metadata.properties下的所有配置信息。
(2)执行
getCandidateConfigurations(annotationMetadata,attributes)会加载所有包下META-INF/spring.factories的信息并组装成Map,然后读取Key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的数组,并将这个数组返回。
(3)执行getExclusions(annotationMetadata,attributes)会获取限制候选配置的所有排除项(找到不希望被自动装配的配置类)。
( 4 ) 执 行 checkExcludedClasses ( configurations ,exclusions)会对参数exclusions进行验证并去除多余的类,它对应@EnableAutoConfiguration注解中的exclusions属性。
(5)执行filter(configurations,autoConfigurationMetadata ) 会 根 据 项 目 中 配 置 的
AutoConfiguration-ImportFilter类进行配置过滤。
通过查看源码,我们可以发现
AutoConfigurationImportFilter是一个接口,OnClassCondition才是它的实现类,而OnClassCondition就是Spring Boot的Condition实现类。@ConditionalOnClass代码如下:
@ConditionalOnClass是基于@Conditional的组合注解,在上述的第(5)步中,Spring Boot可以通过这个注解实现按需加载,只有在@Configuration中符合条件的Class才会被加载进来。@Conditional注解本身是一个元注解,用来标注其他注解,如下所示:
通过利用@Conditional元注解,可以构造满足自己条件的组合条件注解,Spring Boot正是通过这样的方式实现了众多条件注解,实现了基于条件的Bean构造,还有Bean相互依赖情况下的顺序加载,它不需要再通过显性的基于XML文件的依赖文件进行构造。从上述讲解我们可以知道,Spring Boot结合Java元注解概念、Spring底层容器配置机制,以及使用类似Java SPI(Service Provider Interface)机制实现的私有配置加载协议,最终实现了“约定优于配置”。
在Spring Boot的Autoconfigure模块中,还包含了一批这样的组合注解,这些条件的限制在Spring Boot中以注解的形式体现,通常这些条件注解使用@Conditional来配合@Configuration和@Bean等注解来干预Bean的生成,常见的条件注解如下。
- @ConditionalOnBean:Spring容器中存在指定Bean时,实例化当前Bean。
- @ConditionalOnClass:Spring容器中存在指定Class时,实例化当前Bean。
- @ConditionalOnExpression:使用SpEL表达式作为判断条件,满足条件时,实例化当前Bean。
- @ConditionalOnJava:使用JVM版本作为判断条件来实例化当前Bean。
- @ConditionalOnJndi:在JNDI存在时查找指定的位置,满足条件时,实例化当前Bean。
- @ConditionalOnMissingBean:Spring容器中不存在指定Bean时,实例化当前Bean。
- @ConditionalOnMissingClass : Spring 容 器 中 不 存 在 指 定Class时,实例化当前Bean。
- @ConditionalOnNotWebApplication:当前应用不是Web项目时,实例化当前Bean。
- @ConditionalOnProperty:指定的属性是否有指定的值。
- @ConditionalOnResource:类路径是否有指定的值。
- @ConditionalOnSingleCandidate:指定Bean在Spring容器中只有一个。
- @ConditionalOnWebApplication:当前应用是Web项目时,则实例化当前Bean。
有了组合注解,开发人员从大量的XML和Properties中得到了解放,可以抛弃Spring传统的外部配置,使用Spring自动配置,spring�boot-autoconfigure依赖默认配置项,根据添加的依赖自动加载相关的配置属性并启动依赖。应用者只需要引入对应的jar包,SpringBoot就可以自动扫描和加载依赖信息。调整自动配置顺序在Spring Boot的Autoconfigure模块中还可以通过注解对配置和组件的加载顺序做出调整,从而可以让这些存在依赖关系的配置和组件顺利地在Spring容器中被构造出来。
- @AutoConfigureAfter是spring-boot-autoconfigure包下的注解,其作用是将一个配置类在另一个配置类之后加载。
- @AutoConfigureBefore是spring-boot-autoconfigure包下的注解,其作用是将一个配置类在另一个配置类之前加载。
例如,在加载ConfigurationB之后加载ConfigurationA:
○ 实现ConfigurationA.class
○ 实现ConfigurationB.class
○ 创建配置META-INF/spring.factories文件
通过上面的步骤,就可以实现自动调整Bean的加载顺序。另外,Spring为我们提供了@AutoConfigureOrder注解,也可以修改配置文件的加载顺序,示例代码如下:
自动化配置流程
无论是应用初始化还是具体的执行过程,都要调用Spring Boot自动配置模块,下图有助于我们形象地理解自动配置流程。
例 如 , mybatis-spring-boot-starter 、 spring-boot-starter�web等组件的META-INF下均含有spring.factories文件,在自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,最后工厂实例来生成组件所需要的Bean。
Spring Boot功能扩展点详解
在深入分析了Spring Boot的启动过程及其自动装配原理后,我们发现,Spring Boot的启动过程中使用了“模板”模式和“策略”模式,并且利用SpringFactoreisLoader的“私有协议”可以完成很多功能的扩展,满足启动的定制化。
我们将这些主要的扩展点结合源码加以总结,如下图所示。
@EnableAutoConfiguration
从 功 能 扩 展 点 的 角 度 , @EnableAutoConfiguration 借 助SpringFactoriesLoader 可 以 将 标 注 了 @Configuration 注 解 的JavaConfig类汇总并加载到最终的ApplicationContext中,使用条件注解可以在自动化配置过程中定制化Bean的加载过程:
ApplicationListener
ApplicationListener属于Spring框架,它是对Java中监听模式的一种实现方式,如果需要为Spring Boot应用添加我们自定义的ApplicationListener,那么有两种方式:
- 通 过 SpringApplication.addListeners ( … ) 或 者SpringApplication.setListener(…)方法添加一个或者多个自定义的ApplicationListener。
- 借助SpringFactoriesLoader机制,在Spring Boot项目自定义的 META-INF/spring.factories 文 件 中 添 加 配 置 , 以 下 是Spring Boot默认的ApplicationListener配置:
SpringApplicationRunListener
SpringApplicationRunListener的作用是,在整个启动流程中,作为监听者接收不同执行点的事件通知。没有特殊情况一般不需自定义 的 SpringApplicationRunListener 。 SpringApplicationRun�Listener源码如下:
ApplicationContextInitializer
ApplicationContextInitializer也属于Spring框架,它的主要作用是,在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext做刷新(refreshContext)之前,允许我们对ConfiurableApplicationContext的实例做进一步的设置和处理。
不 过 一 般 情 况 下 我 们 不 需 要 自 定 义 一 个
ApplicationContextInitializer,Spring Boot框架默认也只有以下四个实现而已:
CommandLineRunner
CommandLineRunner并不是Spring框架原有的概念,它属于SpringBoot应用特定的回调扩展接口,源码如下:
所有CommandLineRunner的执行时间点是在Spring Boot应用完全初始化之后(这里我们可以认为是Spring Boot应用启动类main方法执行 完 成 之 前 的 最 后 一 步 ) 。 当 前 Spring Boot 应 用 的ApplicationContext中的所有CommandLineRunner都会被加载并执行。
网友评论