一、引言

SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置。

在日常开发中,我们也会自定义一些Starter,特别是现在微服务框架,我们一个项目分成了多个单体项目,而这些单体项目中会引用公司的一些组件,这个时候我们定义Starter,可以使这些单体项目快速搭起,我们只需要关注业务开发。

在此之前我们再深入的了解下SpringBoot启动原理。而后再将如何自定义starter。

二、 启动原理

要想了解启动原理,我们可以Debug模式跟着代码一步步探究,我们从入口方法开始:

  1. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
  2. String[] args) {
  3. return new SpringApplication(primarySources).run(args);
  4. }

这里是创建一个SpringApplication对象,并调用了run方法

2.1 创建SpringApplication对象

  1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. Assert.notNull(primarySources, "PrimarySources must not be null");
  4. //保存主配置类
  5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  6. //确定web应用类型
  7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  8. //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
  9. setInitializers((Collection) getSpringFactoriesInstances(
  10. ApplicationContextInitializer.class));
  11. //从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener
  12. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  13. //从多个配置类中找到有main方法的主配置类
  14. this.mainApplicationClass = deduceMainApplicationClass();
  15. }

从这个方法中可以看出,这个

第一步:保存主配置类。

第二步:确定web应用类型。

第三步:setInitializers方法,这个方法走我们看带入的参数是getSpringFactoriesInstances(ApplicationContextInitializer.class),我们再往下查看getSpringFactoriesInstances

再进入这个方法:

这里就是从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然后再保存起来,放开断点,我们可以看到这个时候获取到的

第四步:从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener,原理也基本类似,进入断点

第五步:从多个配置类中找到有main方法的主配置类。这个执行完之后,SpringApplication就创建完成

2.2 run方法

先贴出代码

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. ConfigurableApplicationContext context = null;
  5. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  6. configureHeadlessProperty();
  7. //从类路径下META-INF/spring.factories获取SpringApplicationRunListeners
  8. SpringApplicationRunListeners listeners = getRunListeners(args);
  9. //回调所有的获取SpringApplicationRunListener.starting()方法
  10. listeners.starting();
  11. try {
  12. //封装命令行参数
  13. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  14. args);
  15. //准备环境
  16. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  17. applicationArguments);//创建环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
  18. configureIgnoreBeanInfo(environment);
  19. //打印Banner图
  20. Banner printedBanner = printBanner(environment);
  21. //创建ApplicationContext,决定创建web的ioc还是普通的ioc
  22. context = createApplicationContext();
  23. //异常分析报告
  24. exceptionReporters = getSpringFactoriesInstances(
  25. SpringBootExceptionReporter.class,
  26. new Class[] { ConfigurableApplicationContext.class }, context);
  27. //准备上下文环境,将environment保存到ioc中
  28. //applyInitializers():回调之前保存的所有的ApplicationContextInitializer的initialize方法
  29. //listeners.contextPrepared(context)
  30. //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded()
  31. prepareContext(context, environment, listeners, applicationArguments,
  32. printedBanner);
  33. //刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat)
  34. //扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)
  35. refreshContext(context);
  36. afterRefresh(context, applicationArguments);
  37. stopWatch.stop();
  38. if (this.logStartupInfo) {
  39. new StartupInfoLogger(this.mainApplicationClass)
  40. .logStarted(getApplicationLog(), stopWatch);
  41. }
  42. //所有的SpringApplicationRunListener回调started方法
  43. listeners.started(context);
  44. //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调,
  45. //ApplicationRunner先回调,CommandLineRunner再回调
  46. callRunners(context, applicationArguments);
  47. }
  48. catch (Throwable ex) {
  49. handleRunFailure(context, ex, exceptionReporters, listeners);
  50. throw new IllegalStateException(ex);
  51. }
  52.  
  53. try {
  54. //所有的SpringApplicationRunListener回调running方法
  55. listeners.running(context);
  56. }
  57. catch (Throwable ex) {
  58. handleRunFailure(context, ex, exceptionReporters, null);
  59. throw new IllegalStateException(ex);
  60. }
  61. //整个SpringBoot应用启动完成以后返回启动的ioc容器
  62. return context;
  63. }

前面的代码不用分析,主要是准备对象,我们从 SpringApplicationRunListeners listeners = getRunListeners(args)开始分析,

第一步:是从类路径下META-INF/spring.factories获取SpringApplicationRunListeners,

这个方法跟前面分析的两个获取配置方法类似。

第二步:回调所有的获取SpringApplicationRunListener.starting()方法。

第三步: 封装命令行参数。

第四步:准备环境,调用prepareEnvironment方法。

第五步:打印Banner图(就是启动时的标识图)。

第六步:创建ApplicationContext,决定创建web的ioc还是普通的ioc。

第七步:异常分析报告。

第八步:准备上下文环境,将environment保存到ioc中,这个方法需要仔细分析下,我们再进入这个方法

这里面有一个applyInitializers方法,这里是回调之前保存的所有的ApplicationContextInitializer的initialize方法

还有一个listeners.contextPrepared(context),这里是回调所有的SpringApplicationRunListener的contextPrepared(),

最后listeners.contextLoaded(context) 是prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded()。

第九步:刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat),这个就是扫描,创建,加载所有组件的地方,(配置类,组件,自动配置)。

第十步:所有的SpringApplicationRunListener回调started方法。

第十一步:从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调,ApplicationRunner先回调,CommandLineRunner再回调。

第十二步:所有的SpringApplicationRunListener回调running方法。

第十三步:整个SpringBoot应用启动完成以后返回启动的ioc容器。

这就是run的全部过程,想要更详细的了解还需自己去看源码。

三、自定义starter

自定义starter(场景启动器),我们要做的事情是两个:确定依赖和编写自动配置。我们重点要做的就是编写自动配置,我们之前写过一些自动配置,主要是注解配置的使用,主要的注解有:

  • @Configuration :指定这个类是一个配置类

  • @ConditionalOnXXX :在指定条件成立的情况下自动配置类生效

  • @AutoConfigureAfter:指定自动配置类的顺序

  • @Bean:给容器中添加组件

  • @ConfigurationPropertie:结合相关xxxProperties类来绑定相关的配置

  • @EnableConfigurationProperties:让xxxProperties生效加入到容器中

按照这些注解写好自动配置类后,我们还需要进行自动配置的加载,加载方式是将需要启动就加载的自动配置类,配置在META-INF/spring.factories,启动器的大致原理是如此,而启动器的实际设计是有一定模式的,就是启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,而自动配置模块应该再重新设计一个,然后启动器再去引用这个自动配置模块。Springboot就是如此设计的:

另外还有一个命名规则:

官方命名空间

– 前缀:“spring-boot-starter-”

– 模式:spring-boot-starter-模块名

– 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

自定义命名空间

– 后缀:“-spring-boot-starter”

– 模式:模块-spring-boot-starter

– 举例:mybatis-spring-boot-starter

3.1 创建自定义starter

第一步:因为我们需要创建两个模块,所以先新建一个空的项目,然后以模块形式创建两个模块。

第二步:再创建两个模块,一个starter和一个自动配置模块

具体的创建过程就不赘述了,就是最简单的项目,去掉不需要的文件,创建完成结构如下:

第三步:我们先将自动配置模块导入starter中,让启动模块依赖自动配置模块

启动模块的POM文件加入依赖

  1. <dependencies>
  2. <!--引入自动配置模块-->
  3. <dependency>
  4. <groupId>com.yuanqinnan-starter</groupId>
  5. <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. </dependency>
  8. </dependencies>

自动配置模块的完整POM文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.4.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.yuanqinnan-starter</groupId>
  12. <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <packaging>jar</packaging>
  15. <properties>
  16. <java.version>1.8</java.version>
  17. </properties>
  18.  
  19. <dependencies>
  20. <!--引入spring-boot-starter;所有starter的基本配置-->
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter</artifactId>
  24. </dependency>
  25. </dependencies>
  26. </project>

至此,两个项目基本创建完成,现在我们实现简单的配置。

第五步:对自动配置类进行自动配置代码编写

先编写一个配置类,用于配置:

  1. @ConfigurationProperties(prefix = "yuanqinnan.hello")
  2. public class HelloProperties {
  3. //前缀
  4. private String prefix;
  5. //后缀
  6. private String suffix;
  7.  
  8. public String getPrefix() {
  9. return prefix;
  10. }
  11.  
  12. public void setPrefix(String prefix) {
  13. this.prefix = prefix;
  14. }
  15.  
  16. public String getSuffix() {
  17. return suffix;
  18. }
  19.  
  20. public void setSuffix(String suffix) {
  21. this.suffix = suffix;
  22. }
  23. }

再编写一个服务

  1. public class HelloService {
  2.  
  3. HelloProperties helloProperties;
  4.  
  5. public HelloProperties getHelloProperties() {
  6. return helloProperties;
  7. }
  8.  
  9. public void setHelloProperties(HelloProperties helloProperties) {
  10. this.helloProperties = helloProperties;
  11. }
  12.  
  13. public String sayHello(String name) {
  14. return helloProperties.getPrefix() + "-" + name + helloProperties.getSuffix();
  15. }
  16. }

然后再将这个服务注入组件:

  1. @Configuration
  2. @ConditionalOnWebApplication //web应用才生效
  3. @EnableConfigurationProperties(HelloProperties.class)
  4. public class HelloServiceAutoConfiguration {
  5.  
  6. @Autowired
  7. HelloProperties helloProperties;
  8. @Bean
  9. public HelloService helloService(){
  10. HelloService service = new HelloService();
  11. service.setHelloProperties(helloProperties);
  12. return service;
  13. }
  14. }

这个时候我们的自动配置以及写完,还差最后一步,因为SpringBoot读取自动配置是在META-INF的spring.factories文件中,所以我们还要将我们的自动配置类写入其中

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. com.yuanqinnan.starter.HelloServiceAutoConfiguration

最后的结构如下:

至此,代码以及编写完成,这个时候我们将其装入仓库中,让其他项目引用

3.2 使用自定义starter

创建一个web项目,然后在项目中引入依赖

  1. <!--引入自定义starter-->
  2. <dependency>
  3. <groupId>com.yuanqinnan.starter</groupId>
  4. <artifactId>yuanqinnan-springboot-starter</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>

在application.properties 配置中加上配置:

  1. yuanqinnan.hello.prefix=早安
  2. yuanqinnan.hello.suffix=晚安

加入测试:

  1. @Autowired
  2. HelloService helloService;

  3. @Test
  4. public void contextLoads() {
  5. System.out.println(helloService.sayHello("世界"));
  6. }

这样自定义Starter和引用自定义都已完成,Springboot的核心知识已经总结完成,后面再进行Springboot的一些高级场景整合,如缓存、消息、检索、分布式等。

SpringBoot之旅第六篇-启动原理及自定义starter的更多相关文章

  1. 【SpringBoot1.x】SpringBoot1.x 启动配置原理 和 自定义starter

    SpringBoot1.x 启动配置原理 和 自定义starter 启动配置原理 本节源码 启动过程主要为: new SpringApplication(sources) 创建 SpringAppli ...

  2. SpringBoot之旅第四篇-web开发

    一.引言 有了自动配置,springboot使web开发变得简单,这个在springboot之旅中的第一篇中就有体现,实际的开发中当然不会这么简单,很多时候我们都需要自己去定制一些东西.web开发的东 ...

  3. java框架之SpringBoot(10)-启动流程及自定义starter

    启动流程 直接从 SpringBoot 程序入口的 run 方法看起: public static ConfigurableApplicationContext run(Object source, ...

  4. SpringBoot源码学习系列之启动原理简介

    本博客通过debug方式简单跟一下Springboot application启动的源码,Springboot的启动源码是比较复杂的,本博客只是简单梳理一下源码,浅析其原理 为了方便跟源码,先找个Ap ...

  5. SpringBoot之旅第五篇-数据访问

    一.引言 大部分系统都离不开数据访问,数据库包括SQL和NOSQL,SQL是指关系型数据库,常见的有SQL Server,Oracle,MySQL(开源),NOSQL是泛指非关系型数据库,常见的有Mo ...

  6. SpringBoot非官方教程 | 第六篇:springboot整合mybatis

    转载请标明出处: 原文首发于:https://www.fangzhipeng.com/springboot/2017/07/11/springboot-mybatis/ 本文出自方志朋的博客 本文主要 ...

  7. SpringBoot之旅第三篇-日志

    一.前言 日志对于一个系统的重要性不言而喻,日志能帮我们快速定位线上问题,市场上存在非常多的日志框架,比较常见的有 JUL,JCL,Log4j,Log4j2,Logback.SLF4j.jboss-l ...

  8. Spring之旅第六篇-事务管理

    一.什么是事务 什么是事务(Transaction)?事务是数据库中的概念,是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit). 有个非常经典的转账问题:A向B转款1000元,A转出成 ...

  9. SpringBoot之旅第七篇-Docker

    一.引言 记得上大三时,要给微机房电脑安装系统,除了原生的操作系统外,还要另外安装一些必要的开发软件,如果每台电脑都重新去安装的话工作量就很大了,这个时候就使用了windows镜像系统,我们将要安装的 ...

随机推荐

  1. Github Page 绑定域名

    http://kyle.xlau.org/posts/github-cname.html CNAME 创建一个CNAME文件,内容是你的域名,如: xlau.org 然后把此文件添加到Github仓库 ...

  2. Qt中的ui指针和this指针

    初学qt,对其ui指针和this指针产生疑问,画了个把小时终于搞懂了. 首先看ui指针的定义: 在mainwindow.h中 private: Ui::MainWindow *ui; Ui又是什么? ...

  3. Linq小整理

    Linq(Language Integrated Query)中文翻译为语言集成查询 (1)源起 .net的设计者在类库中定义了一系列的扩展方法 来方便用户操作集合对象 这些扩展方法构成了LINQ的查 ...

  4. 成功实现在VS2017下编译含<pthread.h>的代码:

    VS2017配置使用#<pthread.h> https://blog.csdn.net/cry1994/article/details/79115394(原来SystemWow64里面存 ...

  5. mysql 30大优化策略

    mysql 30大优化策略 1.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在 wher ...

  6. 解决jequry使用keydown无法跳转的问题

    问题描述 代码 <script> $("document").ready(function() { $("#button").click(funct ...

  7. 更新版PowerBI发布了-- Power BI Report Server Update – March 2018

    新版的PowerBI server 和 Desktop 终于发布了.  详细增加功能见以下链接: 下载最新版PowerBI Report Server: https://powerbi.microso ...

  8. Unity3D学习(二):使用JSON进行对象数据的存储读取

    前言 前段时间完成了自己的小游戏Konster的制作,今天重新又看了下代码.原先对关卡解锁数据的存储时用了Unity自带的PlayerPref(字典式存储数据). 读取关卡数据的代码: void Aw ...

  9. Struts标签库详解【3】

    struts2标签库详解 要在jsp中使用Struts2的标志,先要指明标志的引入.通过jsp的代码的顶部加入以下的代码: <%@taglib prefix="s" uri= ...

  10. 小苹果WP(实验吧-隐写术)

    本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=706 前言:本文是实验吧训练题库里隐写术部分的一道题:"小苹果"的Write Up. 题目链接: ...