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

启动配置原理

本节源码

启动过程主要为:

  • new SpringApplication(sources) 创建 SpringApplication 对象
  • springApplication.run() 运行Spring应用程序,创建并刷新一个新的应用环境
  • 整个过程使用了事件监听机制

创建 SpringApplication 对象

  1. SpringApplication.run(StartStarterApplication.class, args);
  2. public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
  3. return new SpringApplication(sources).run(args);
  4. }
  5. public SpringApplication(Object... sources) {
  6. initialize(sources);
  7. }
  8. private void initialize(Object[] sources) {
  9. // 保存主配置类信息
  10. if (sources != null && sources.length > 0) {
  11. this.sources.addAll(Arrays.asList(sources));
  12. }
  13. // 判断当前是否是一个 Web App
  14. this.webEnvironment = deduceWebEnvironment();
  15. // 从类路径下找到 META_INF/spring.factories 配置文件的 ApplicationContextInitializer,然后保存起来
  16. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  17. // 从类路径下找到 META_INF/spring.factories 配置文件的 ApplicationListener,然后保存起来
  18. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  19. // 从多个配置类中找到有 main() 的主配置类
  20. this.mainApplicationClass = deduceMainApplicationClass();
  21. }

运行Spring应用程序,创建并刷新一个新的应用环境

  1. public ConfigurableApplicationContext run(String... args) {
  2. StopWatch stopWatch = new StopWatch();
  3. stopWatch.start();
  4. ConfigurableApplicationContext context = null;
  5. FailureAnalyzers analyzers = null;
  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(args);
  14. // 准备环境
  15. // 准备环境完成后,回调 SpringApplicationRunListener.environmentPrepared() 表示环境准备完成
  16. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  17. // 打印 Spring 标志
  18. Banner printedBanner = printBanner(environment);
  19. // 创建 ApplicationContext,决定创建 web 的ioc,还是普通的 ioc
  20. context = createApplicationContext();
  21. analyzers = new FailureAnalyzers(context);
  22. // 准备上下文环境,将 environment 保存到 ioc 中,而且调用 applyInitializers()
  23. // 这个方法将 回调之前保存的所有的 ApplicationContextInitializer 的 initialize()
  24. // 和 回调之前保存的所有的 SpringApplicationRunListener 的 contextPrepared()
  25. // 准备上下文环境完成后,回调之前保存的所有的 SpringApplicationRunListener 的 contextLoaded()
  26. // 控制台打印:使用 PID 6894 在 192.168.0.103 上启动 主配置类
  27. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  28. // 刷新容器,即 ioc 容器初始化,如果是 web app 还会创建嵌入式的 Tomcat
  29. refreshContext(context);
  30. // 从 ioc 容器中获取所有的 ApplicationRunner 和 CommandLineRunner 进行回调
  31. afterRefresh(context, applicationArguments);
  32. // 所有的 SpringApplicationRunListener 回调 finished()
  33. listeners.finished(context, null);
  34. stopWatch.stop();
  35. if (this.logStartupInfo) {
  36. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  37. }
  38. // 整个 SpringBoot 应用启动完成以后返回启动的 ioc 容器
  39. return context;
  40. }
  41. catch (Throwable ex) {
  42. handleRunFailure(context, listeners, analyzers, ex);
  43. throw new IllegalStateException(ex);
  44. }
  45. }

事件监听机制

ApplicationContextInitializer、SpringApplicationRunListener 配置在 META-INF/spring.factories 中。

ApplicationRunner、CommandLineRunner 放在 ioc 容器中。


HelloApplicationContextInitializer:

  1. public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  2. @Override
  3. public void initialize(ConfigurableApplicationContext applicationContext) {
  4. System.out.println("ApplicationContextInitializer...initialize..."+applicationContext);
  5. }
  6. }

HelloSpringApplicationRunListener:

  1. public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
  2. // 必须有一个构造器
  3. public HelloSpringApplicationRunListener(SpringApplication application, String[] arg) {
  4. }
  5. @Override
  6. public void starting() {
  7. System.out.println("SpringApplicationRunListener...starting...");
  8. }
  9. @Override
  10. public void environmentPrepared(ConfigurableEnvironment environment) {
  11. System.out.println("SpringApplicationRunListener...environmentPrepared.." + environment);
  12. Object o = environment.getSystemProperties().get("os.name");
  13. System.out.println("SpringApplicationRunListener...environmentPrepared.. os.name "+o);
  14. }
  15. @Override
  16. public void contextPrepared(ConfigurableApplicationContext context) {
  17. System.out.println("SpringApplicationRunListener...contextPrepared...");
  18. }
  19. @Override
  20. public void contextLoaded(ConfigurableApplicationContext context) {
  21. System.out.println("SpringApplicationRunListener...contextLoaded...");
  22. }
  23. @Override
  24. public void finished(ConfigurableApplicationContext context, Throwable exception) {
  25. System.out.println("SpringApplicationRunListener...finished...");
  26. }
  27. }

将它们配置在 META-INF/spring.factories 中:

src/main/resources/META-INF/spring.factories

  1. org.springframework.context.ApplicationContextInitializer=\
  2. cn.parzulpan.listener.HelloApplicationContextInitializer
  3. org.springframework.boot.SpringApplicationRunListener=\
  4. cn.parzulpan.listener.HelloSpringApplicationRunListener

HelloApplicationRunner:

  1. @Component
  2. public class HelloApplicationRunner implements ApplicationRunner {
  3. @Override
  4. public void run(ApplicationArguments args) throws Exception {
  5. System.out.println("ApplicationRunner...run...." + args);
  6. }
  7. }

HelloCommandLineRunner:

  1. @Component
  2. public class HelloCommandLineRunner implements CommandLineRunner {
  3. @Override
  4. public void run(String... args) throws Exception {
  5. System.out.println("CommandLineRunner...run..."+ Arrays.asList(args));
  6. }
  7. }

将它们放置在 ioc 容器中。


运行主配置类,观察打印输出,可以得到上面的结论。

自定义 starter

SpringBoot 最大的特点就是引入非常多的场景启动器,想使用那个场景就可以直接整合。

它也支持自定义场景启动器,比如 mybatis-spring-boot-starter

编写自动配置需要的必有项

  1. @Configuration // 指定这个类是一个配置类
  2. @ConditionalOnXXX // 在指定条件成立的情况下自动配置类生效
  3. @AutoConfigureAfter // 指定自动配置类的顺序
  4. @Bean // 给容器中添加组件
  5. @ConfigurationPropertie // 结合相关 xxxProperties 类来绑定相关的配置
  6. @EnableConfigurationProperties // 让 xxxProperties 生效并加入到容器中

自动配置类要能加载将需要启动就加载的自动配置类,配置在 META‐INF/spring.factories

  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

模式总结

  • 启动器只用来做依赖导入 xx-spring-boot-starter
  • 编写一个自动配置模块 xx-spring-boot-starter-autoconfigurer
  • 启动器依赖自动配置模块,别人使用只需要引入启动器
  • 官方命名空间: spring-boot-starter-模块名, 自定义命名空间:模块名-spring-boot-starter

自定义步骤

本节源码

前期准备:创建一个空项目 custom-starter ,向其加入一个 Maven 工厂

parzulpan-spring-boot-starter 模块,在加入一个 springboot 类型的 parzulpan-spring-boot-starte 模块。

启动器模块

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>cn.parzulpan</groupId>
  7. <artifactId>parzulpan-spring-boot-starter</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <!-- 启动器 -->
  10. <dependencies>
  11. <!-- 依赖自动配置模块 -->
  12. <dependency>
  13. <groupId>cn.parzulpan</groupId>
  14. <artifactId>parzulpan-spring-boot-starter-configurer</artifactId>
  15. <version>0.0.1-SNAPSHOT</version>
  16. </dependency>
  17. </dependencies>
  18. </project>

自动配置模块

  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 https://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>1.5.22.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>cn.parzulpan</groupId>
  12. <artifactId>parzulpan-spring-boot-starter-configurer</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>parzulpan-spring-boot-starter-configurer</name>
  15. <description>parzulpan starter configurer</description>
  16. <properties>
  17. <project.build.sourceEncoding>UTF‐8</project.build.sourceEncoding>
  18. <project.reporting.outputEncoding>UTF‐8</project.reporting.outputEncoding>
  19. <java.version>1.8</java.version>
  20. </properties>
  21. <dependencies>
  22. <!-- 引入spring‐boot‐starter,它是所有 starter 的基本配置 -->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter</artifactId>
  26. </dependency>
  27. </dependencies>
  28. </project>

编写业务类 HelloService:

  1. package cn.parzulpan;
  2. /**
  3. * @Author : parzulpan
  4. * @Time : 2020-12
  5. * @Desc : HelloService
  6. */
  7. public class HelloService {
  8. HelloServiceProperties helloServiceProperties;
  9. public HelloServiceProperties getHelloServiceProperties() {
  10. return helloServiceProperties;
  11. }
  12. public void setHelloServiceProperties(HelloServiceProperties helloServiceProperties) {
  13. this.helloServiceProperties = helloServiceProperties;
  14. }
  15. public String sayHelloName(String name) {
  16. return helloServiceProperties.getPrefix() + " - " + name + " - " + helloServiceProperties.getSuffix();
  17. }
  18. }

编写属性类 HelloServiceProperties:

  1. /**
  2. * @Author : parzulpan
  3. * @Time : 2020-12
  4. * @Desc : HelloService 属性类
  5. */
  6. @ConfigurationProperties(prefix = "parzulpan.hello")
  7. public class HelloServiceProperties {
  8. private String prefix; // 前置语
  9. private String suffix; // 后置语
  10. public String getPrefix() {
  11. return prefix;
  12. }
  13. public void setPrefix(String prefix) {
  14. this.prefix = prefix;
  15. }
  16. public String getSuffix() {
  17. return suffix;
  18. }
  19. public void setSuffix(String suffix) {
  20. this.suffix = suffix;
  21. }
  22. }

编写配置文件 src/main/resources/META-INF/spring.factories:

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. cn.parzulpan.HelloServiceAutoConfiguration

编写自动配置类 HelloServiceAutoConfiguration:

  1. /**
  2. * @Author : parzulpan
  3. * @Time : 2020-12
  4. * @Desc : HelloService 自动配置类
  5. */
  6. @Configuration
  7. @ConditionalOnWebApplication // web app 才有效
  8. @EnableConfigurationProperties(HelloServiceProperties.class) // 让 HelloServiceProperties 生效并加入到容器中
  9. public class HelloServiceAutoConfiguration {
  10. @Autowired
  11. HelloServiceProperties helloServiceProperties;
  12. @Bean
  13. public HelloService helloService() {
  14. HelloService helloService = new HelloService();
  15. helloService.setHelloServiceProperties(helloServiceProperties);
  16. return helloService;
  17. }
  18. }

将这两个模块分别 install 到本地,然后测试使用,创建一个 SpringBoot Web 项目 custom-starter-test ,引入自定义 starter。测试源码

  1. <!-- 引入自定义 starter -->
  2. <dependency>
  3. <groupId>cn.parzulpan</groupId>
  4. <artifactId>parzulpan-spring-boot-starter</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>

编写配置文件 application.properties:

  1. parzulpan.hello.prefix=PARZULPAN
  2. parzulpan.hello.suffix=HELLO WORLD

编写控制类:

  1. /**
  2. * @Author : parzulpan
  3. * @Time : 2020-12
  4. * @Desc :
  5. */
  6. @RestController
  7. public class HelloController {
  8. @Autowired
  9. HelloService helloService;
  10. // http://localhost:8080/hello
  11. @GetMapping("/hello")
  12. public String hello() {
  13. return helloService.sayHelloName("curry");
  14. }
  15. }

练习和总结

【SpringBoot1.x】SpringBoot1.x 启动配置原理 和 自定义starter的更多相关文章

  1. Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件

    本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...

  2. SpringBoot的启动配置原理

    一.启动流程 创建SpringApplication对象 public class SpringApplication { public SpringApplication(Class... prim ...

  3. springboot 启动配置原理【转】【补】

    创建应用 几个重要的事件回调机制  , 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunL ...

  4. SpringBoot之旅第六篇-启动原理及自定义starter

    一.引言 SpringBoot的一大优势就是Starter,由于SpringBoot有很多开箱即用的Starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置. 在日常开发中,我们也会自 ...

  5. SpringBoot启动源码及自定义starter

    为什么springboot工程能够在mian方法中完成启动呢?需要大家掌握的有几个点:1.SPISPI在springboot中是去读取META-INF/spring.factories目录的配置文件内 ...

  6. springboot启动配置原理之二(运行run方法)

    public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); s ...

  7. springboot启动配置原理之一(创建SpringApplication对象)

    几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener ...

  8. 七、Spring Boot 启动配置原理

    几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener ...

  9. Spring boot 启动配置原理

    配置在META-INF/spring.factories 有几个主要的类 ApplicationContextInitializer    创建SpringAplication SpringAppli ...

随机推荐

  1. 中间件面试专题:kafka高频面试问题

  2. 【学习笔记】动态 dp 入门简易教程

    序列 dp 引入:最大子段和 给定一个数列 \(a_1, a_2, \cdots, a_n\)(可能为负),求 \(\max\limits_{1\le l\le r\le n}\left\{\sum_ ...

  3. thinkphp thinkphp6 安装JWT

    第一步:composer安装   composer require firebase/php-jwt 下图是执行成功 cd 进入项目目录的vendor 找到firebase 看到下面有一个php-jw ...

  4. oracle查年度周末日期

    1.查年度周末日期sql SELECT distinct TRUNC(TO_DATE('2019-01-01','yyyy-mm-dd')+ rownum,'iw')+ 5 AS sat, TRUNC ...

  5. Object.assign 之后 点对象 找不到

    export function CopyObject(val) {   return JSON.parse(JSON.stringify(val)); }

  6. 云图说 | 云上资源管控有神器!关于IAM,你想知道的都在这里!

    摘要:统一身份认证(Identity and Access Management,简称IAM)是华为云上帮助您安全控制华为云资源访问权限的基础服务.通过本期云图说,您可以初步了解IAM的基本功能. 从 ...

  7. ES6中的Promise和Generator详解

    目录 简介 Promise 什么是Promise Promise的特点 Promise的优点 Promise的缺点 Promise的用法 Promise的执行顺序 Promise.prototype. ...

  8. Web服务器-并发服务器-协程 (3.4.2)

    @ 目录 1.分析 2.代码 关于作者 1.分析 随着网站的用户量越来愈多,通过多进程多线程的会力不从心 使用协程可以缓解这一问题 只要使用gevent实现 2.代码 from socket impo ...

  9. pandas的学习5-导入导出数据

    import pandas as pd ''' pandas可以读取与存取的资料格式有很多种,像csv.excel.json.html与pickle等-, 详细请看官方说明文件 ''' # read ...

  10. Centos7__Scrapy + Scrapy_redis 用Docker 实现分布式爬虫

    原理:其实就是用到redis的优点及特性,好处自己查--- 1,scrapy 分布式爬虫配置: settings.py BOT_NAME = 'first' SPIDER_MODULES = ['fi ...