一、单元测试

Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
  1. 使用添加JUnit 5,添加对应的starter(自动引入)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  1. Spring的JUnit 5的基本单元测试模板
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootApplicationTests {

    @Autowired
    private Component component;
    
    @Test
    //@Transactional 标注后连接数据库有回滚功能
    public void contextLoads() {
		Assertions.assertEquals(5, component.getFive());
    }
}

1. Junit5常用注解

  • @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试,省略main方法
  • @ParameterizedTest:表示方法是参数化测试。
  • @RepeatedTest:表示方法可重复执行。
  • @DisplayName:为测试类或者测试方法设置展示名称
  • @BeforeEach:表示在每个单元测试之前执行。
  • @AfterEach:表示在每个单元测试之后执行。
  • @BeforeAll:表示在所有单元测试之前执行。
  • @AfterAll:表示在所有单元测试之后执行。
  • @Tag:表示单元测试类别,类似于JUnit4中的@Categories。
  • @Disabled:表示测试类或测试方法不执行。
  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
  • @SpringBootTest:为测试类或测试方法提供Springboot的底层支持,会加载容器
@SpringBootTest
@DisplayName("Junit5功能测试")
public class Junit5Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    /**
     * 重复测试5次
     */
    @RepeatedTest(value = 5)
    @DisplayName("测试1displayName注解")
    @Test
    void test1() {
        System.out.println(jdbcTemplate);
    }

    @DisplayName("测试2")
    @Test
    void test2() {
        System.out.println(2);
    }

    /**
     * 规定方法超时时间,超出时间测试出异常
     * @throws InterruptedException
     */
    @Disabled
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTime() throws InterruptedException {
        Thread.sleep(600);
    }

    @BeforeEach
    void testBeforeEach(){
        System.out.println("测试就要开始了");
    }

    @AfterEach
    void testAfterEach(){
        System.out.println("测试结束了");
    }

    @BeforeAll
    static void testBeforeAll(){
        System.out.println("启动.....");
    }
    @AfterAll
    static void testAfterAll(){
        System.out.println("结束.....");
    }
}

2. 断言

断言Assertion是测试方法中的核心部分,用来对测试需要满足的条件进行验证。

这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。

检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。

JUnit 5 内置的断言可以分成如下几个类别:

  • 简单断言

用来对单个值进行简单的验证。如:

方法

说明

assertEquals

判断两个对象或两个原始类型是否相等

assertNotEquals

判断两个对象或两个原始类型是否不相等

assertSame

判断两个对象引用是否指向同一个对象

assertNotSame

判断两个对象引用是否指向不同的对象

assertTrue

判断给定的布尔值是否为 true

assertFalse

判断给定的布尔值是否为 false

assertNull

判断给定的对象引用是否为 null

assertNotNull

判断给定的对象引用是否不为 null

/**
     * 断言:前面的断言失败,后面的代码都不会执行
     */
@Test
@DisplayName("simple assertion")
public void simple() {
    assertEquals(3, 1 + 2, "simple math");
    assertNotEquals(3, 1 + 1);

    assertNotSame(new Object(), new Object());
    Object obj = new Object();
    assertSame(obj, obj);

    assertFalse(1 > 2);
    assertTrue(1 < 2);

    assertNull(null);
    assertNotNull(new Object());
}
  • 数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。

@Test
@DisplayName("array assertion")
public void array() {
	assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
  • 组合断言

assertAll()方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。

@Test
@DisplayName("assert all")
public void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1,"结果不是2"),
    () -> assertTrue(1 > 0,“结果不为真”)
 );
}
  • 异常断言

JUnit5提供了一种新的断言方式Assertions.assertThrows(),配合函数式编程就可以进行使用。

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
           //扔出断言异常
            ArithmeticException.class, () -> System.out.println(1 % 0));
}
  • 超时断言

JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
  • 快速失败

通过 fail 方法直接使得测试失败。

@Test
@DisplayName("fail")
public void shouldFail() {
	fail("This should fail");
}

在实际开发中,项目上线前,可以在Maven中跑一遍test,得到完整的测试报告。

3. 前置条件

不符合断言会报告测试失败,不符合前置条件会被报告测试中止

@DisplayName("前置条件测试")
@Test
void testAssumptions(){
    Assumptions.assumeFalse(true,"结果不是True");
}

4. 嵌套测试

通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。

内层的test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行,外层的不能驱动

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

5. 参数化测试

用不同的参数多次运行测试

利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
  • 此外Junit5可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现**ArgumentsProvider**接口,任何外部文件都可以作为它的入参
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
void testParameterized(int i){
    System.out.println(i);
}

@ParameterizedTest
@DisplayName("参数化测试2")
@MethodSource("StringProvider")
void testParameterized2(String str){
    System.out.println(str);
}
static Stream<String> StringProvider() {
    return Stream.of("apple", "banana","orange");
}

二、指标监控

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。

SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

官方文档 - Spring Boot Actuator: Production-ready Features

1. 如何使用

  • 添加依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 访问http://localhost:8080/actuator/**
  • 暴露所有监控信息为HTTP。
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露
  • 测试例子

2. Actuator EndPoint

2.1 常使用的端点

ID

描述

auditevents

暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件

beans

显示应用程序中所有Spring Bean的完整列表。

caches

暴露可用的缓存。

conditions

显示自动配置的所有条件信息,包括匹配或不匹配的原因。

configprops

显示所有@ConfigurationProperties

env

暴露Spring的属性ConfigurableEnvironment

flyway

显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。

health

显示应用程序运行状况信息。

httptrace

显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。

info

显示应用程序信息。

integrationgraph

显示Spring integrationgraph 。需要依赖spring-integration-core

loggers

显示和修改应用程序中日志的配置。

liquibase

显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。

metrics

显示当前应用程序的“指标”信息。

mappings

显示所有@RequestMapping路径列表。

scheduledtasks

显示应用程序中的计划任务。

sessions

允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。

shutdown

使应用程序正常关闭。默认禁用。

startup

显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup

threaddump

执行线程转储。

如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

ID

描述

heapdump

返回hprof堆转储文件。

jolokia

通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core

logfile

返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。

prometheus

以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

其中最常用的Endpoint:

  • Health:监控状况
  • Metrics:运行时指标
  • Loggers:日志记录

2.2 Health Endpoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告。
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等。
  • 可以很容易的添加自定义的健康检查机制。

2.3 Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到:

  • 通过Metrics对接多种监控系统。
  • 简化核心Metrics开发。
  • 添加自定义Metrics或者扩展已有Metrics。

2.4 管理Endpoints

1. 开启与禁用Endpoints

  • 默认所有的Endpoint除过shutdown都是开启的。
  • 需要开启或者禁用某个Endpoint。配置模式为management.endpoint.<endpointName>.enabled = true
management:
  endpoint:
    beans:
      enabled: true
  • 或者禁用所有的Endpoint然后手动开启指定的Endpoint。
management:
  endpoints:
    enabled-by-default: false
  endpoint:
    beans:
      enabled: true
    health:
      enabled: true

2. 暴露Endpoints

支持的暴露方式

  • HTTP:默认只暴露health和info。
  • JMX:默认暴露所有Endpoint。
  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入Spring Security,则会默认配置安全访问规则。

ID

JMX

Web

auditevents

Yes

No

beans

Yes

No

caches

Yes

No

conditions

Yes

No

configprops

Yes

No

env

Yes

No

flyway

Yes

No

health

Yes

Yes

heapdump

N/A

No

httptrace

Yes

No

info

Yes

Yes

integrationgraph

Yes

No

jolokia

N/A

No

logfile

N/A

No

loggers

Yes

No

liquibase

Yes

No

metrics

Yes

No

mappings

Yes

No

prometheus

N/A

No

scheduledtasks

Yes

No

sessions

Yes

No

shutdown

Yes

No

startup

Yes

No

threaddump

Yes

No

若要更改公开的Endpoint,请配置以下的包含和排除属性:

Property

Default

management.endpoints.jmx.exposure.exclude

management.endpoints.jmx.exposure.include

*

management.endpoints.web.exposure.exclude

management.endpoints.web.exposure.include

info, health

官方文档 - Exposing Endpoints

3. 可视化展示模板

Spring-boot-admin开源项目https://github.com/codecentric/spring-boot-admin

作为监控服务器,监控微服务监控指标,可视化展示

  1. 新建adminserver服务,引入依赖
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.5.1</version>
</dependency>
  1. 在adminserver主程序类上加入@EnableAdminServer注解

防止本地运行时端口冲突,修改服务端口为8088

server:
  port: 8088
  1. 注册客户端
  • pom.xml
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.5.1</version>
</dependency>
  • 客户端配置汇报的adminserver的url地址
boot:
    admin:
      client:
        url: http://localhost:8088
        instance:
          prefer-ip: true  #使用ip注册进来
  application:
    name: boot_admin   #修改应用程序名称
        
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露

三、原理解析

1. Profile功能

1.1 Application-Profile功能

为了方便多环境(开发环境、生产环境、测试环境)适配切换,Spring Boot简化了profile功能。

  • 默认配置文件application.yaml任何时候都会加载。
  • 指定环境配置文件application-{env}.yamlenv通常替代为testprod
  • 激活指定环境
  • 配置文件激活:spring.profiles.active=prod
  • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha(修改配置文件的任意值,命令行优先
  • 默认配置与环境配置同时生效
  • 同名配置项,profile配置优先

1.2 @Profile条件装配功能

public interface Person {

   String getName();
   Integer getAge();

}

@Profile("test")//加载application-test.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Worker implements Person {

    private String name;
    private Integer age;
}

@Profile(value = {"prod","default"})//加载application-prod.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Boss implements Person {

    private String name;
    private Integer age;
}
  • @Profile还可以修饰在方法上
class Color {
}

@Configuration
public class MyConfig {

    @Profile("prod")
    @Bean
    public Color red(){
        return new Color();
    }

    @Profile("test")
    @Bean
    public Color green(){
        return new Color();
    }
}

1.3 Profile分组

可以激活一组,相当于批量加载prod和test:

spring.profiles.active=production

spring.profiles.group.production[0]=prod
spring.profiles.group.production[1]=test

2. 外部化配置

  • 外部配置源
  • Java属性文件。
  • YAML文件。
  • 环境变量。
@Value("${MAVEN_HOME}")
  • 命令行参数。
  • 配置文件查找位置(为了修改之前不敢动的配置)
  1. classpath 根路径。
  2. classpath 根路径下config目录。
  3. jar包当前目录。
  4. jar包当前目录的config目录。
  5. /config子目录的直接子目录。
  • 配置文件加载顺序:
  1. 当前jar包内部的application.propertiesapplication.yml
  2. 当前jar包内部的application-{profile}.propertiesapplication-{profile}.yml
  3. 引用的外部jar包的application.propertiesapplication.yml
  4. 引用的外部jar包的application-{profile}.propertiesapplication-{profile}.yml
  • 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

3. 自定义starter

生产环境中对某个固定功能抽取成一个starter,只需要修改配置文件即可引入

3.1 starter启动原理

  • starter的pom.xml引入autoconfigure依赖



starter

autoconfigure

spring-boot-starter


  • autoconfigure包中配置使用META-INF/spring.factoriesEnableAutoConfiguration的值,使得项目启动加载指定的自动配置类
  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties
  • @Configuration@Conditional@EnableConfigurationProperties@Bean
  • 引入starter — xxxAutoConfiguration — 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

3.2 自定义starter

目标:创建HelloService的自定义starter

1. 搭建工程环境

  • 创建两个工程,分别命名为hello-spring-boot-starter(普通Maven工程)
    hello-spring-boot-starter-autoconfigure(需用用到Spring Initializr创建的Maven工程),即启动器引入自动配置包
  • hello-spring-boot-starter无需编写什么代码,只需让该工程引入hello-spring-boot-starter-autoconfigure依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zju</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.zju</groupId>
            <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
</project>
  • hello-spring-boot-starter-autoconfigure的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zju</groupId>
    <artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello-spring-boot-starter-autoconfigure</name>
    <description>hello-spring-boot-starter-autoconfigure</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

2. 配置hello-spring-boot-starter-autoconfigure工程

  • 创建4个文件:
  • com.zju.hello.auto.HelloServiceAutoConfiguration
/**
 * @EnableConfigurationProperties(HelloProperties.class)有两个功能:
 * 1.默认将HelloProperties放在容器中
 * 2.开启HelloProperties属性绑定
 */
@Configuration
@ConditionalOnMissingBean(HelloService.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        return helloService;
    }
}
  • com.zju.hello.bean.HelloProperties
/**
 * 在配置类中开启属性绑定
 */
//"zju.hello"指定配置属性中的前缀
@ConfigurationProperties("zju.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}
  • com.zju.hello.service.HelloService
/**
 * 默认不要放在容器中,在配置类中条件注入
 */
public class HelloService {

    @Autowired
    HelloProperties helloProperties;

    public String sayHello(String userName){
        return helloProperties.getPrefix()+":"+userName+">>"+helloProperties.getSuffix();
    }
}
  • src/main/resources/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zju.hello.auto.HelloServiceAutoConfiguration

3. 下载到maven仓库中

  • 用maven插件,将两工程cleaninstall到本地maven仓库中

4. 实际开发场景中测试使用自定义starter

  • 用Spring Initializr创建hello-spring-boot-starter-test工程,引入hello-spring-boot-starter依赖:
<dependency>
    <groupId>com.zju</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  • 添加配置文件application.properties
hello.prefix=hello
hello.suffix=666
  • controller层:
package com.zju.boot.controller;

import com.zju.hello.service.HelloService;//来自自定义的starter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String sayHello(){

        String sayHello = helloService.sayHello("fkd");
        return sayHello;
    }
}

注意:自定义放入HelloService组件,实现进一步自定义

@Configuration
public class MyConfig {
    
    @Bean
    public HelloService helloService(){
        //放入自定义的helloService组件
        HelloService helloService = new HelloService();
        
        return helloService;
    }
}

4. Springboot原理

Spring原理、SpringMVC原理、自动配置原理、SpringBoot原理

4.1 Springboot启动过程

  • 创建 SpringApplication
  1. 读取一些组件,保存一些信息。
  2. 判定当前应用的类型。ClassUtils。Servlet
  3. bootstrappers:初始启动引导器(List):去spring.factories文件中找org.springframework.boot.Bootstrapper
  4. ApplicationContextInitializer;去spring.factories找**ApplicationContextInitializer**
  • List<ApplicationContextInitializer<?>> initializers
  1. ApplicationListener ;应用监听器。去spring.factoriesApplicationListener
  • List<ApplicationListener<?>> listeners
  • 运行 SpringApplication
  1. StopWatch
  2. 记录应用的启动时间
  3. **创建引导上下文(Context环境)**createBootstrapContext()
  • 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
  1. 让当前应用进入headless模式。java.awt.headless
  2. 获取所有RunListener(运行监听器)【为了方便所有Listener进行事件感知】
  • getSpringFactoriesInstances 去spring.factoriesSpringApplicationRunListener.
  1. 遍历SpringApplicationRunListener 调用 starting 方法;
  • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
  1. 保存命令行参数;ApplicationArguments
  2. 准备环境 prepareEnvironment();
  • 返回或者创建基础环境信息对象。StandardServletEnvironment
  • 配置环境信息对象。
  • 读取所有的配置源的配置属性值。
  • 绑定环境信息
  • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
  1. 创建IOC容器(createApplicationContext())
  • 根据项目类型(Servlet)创建容器
  • 当前会创建 AnnotationConfigServletWebServerApplicationContext
  1. 准备ApplicationContext IOC容器的基本信息prepareContext()
  • 保存环境信息
  • IOC容器的后置处理流程。
  • 应用初始化器;applyInitializers;
  • 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
  • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
  • 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
  1. **刷新IOC容器。**refreshContext
  • 创建容器中的所有组件(Spring注解)
  1. 容器刷新完成后工作?afterRefresh
  2. 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
  3. **调用所有runners;**callRunners()
  • 获取容器中的 ApplicationRunner
  • 获取容器中的 CommandLineRunner
  • 合并所有runner并且按照@Order进行排序
  • 遍历所有的runner。调用 run 方法
  1. 如果以上有异常,
  • 调用Listener 的 failed
  1. 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
  2. **running如果有问题。继续通知 failed 。**调用所有 Listener 的failed;通知所有的监听器 fai

4.2 自定义Application Events and Listeners

  • MyApplicationContextInitializer.java
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer ....initialize.... ");
    }
}
  • MyApplicationListener.java
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener.....onApplicationEvent...");
    }
}
  • MyApplicationRunner.java
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Order(1)
@Component//放入容器
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("MyApplicationRunner...run...");
    }
}
  • MyCommandLineRunner.java
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 应用启动做一个一次性事情
 */
@Order(2)
@Component//放入容器
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner....run....");
    }
}
  • MySpringApplicationRunListener.java
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    private SpringApplication application;
    public MySpringApplicationRunListener(SpringApplication application, String[] args){
        this.application = application;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("MySpringApplicationRunListener....starting....");

    }


    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("MySpringApplicationRunListener....environmentPrepared....");
    }


    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....contextPrepared....");

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....contextLoaded....");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....started....");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener....running....");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("MySpringApplicationRunListener....failed....");
    }
}
  • 注册MyApplicationContextInitializerMyApplicationListenerMySpringApplicationRunListener:

resources / META-INF / spring.factories:

org.springframework.context.ApplicationContextInitializer=\
  com.lun.boot.listener.MyApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  com.lun.boot.listener.MyApplicationListener

org.springframework.boot.SpringApplicationRunListener=\
  com.lun.boot.listener.MySpringApplicationRunListener