一、单元测试
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
- 使用添加JUnit 5,添加对应的starter(自动引入)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- 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方式暴露
- 测试例子
- http://localhost:8080/actuator/beans
- http://localhost:8080/actuator/configprops
- http://localhost:8080/actuator/metrics
- http://localhost:8080/actuator/metrics/jvm.gc.pause
- http://localhost:8080/actuator/metrics/endpointName/detailPath
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.name 或logging.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 |
3. 可视化展示模板
Spring-boot-admin开源项目https://github.com/codecentric/spring-boot-admin
作为监控服务器,监控微服务监控指标,可视化展示
- 新建adminserver服务,引入依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.5.1</version>
</dependency>
- 在adminserver主程序类上加入
@EnableAdminServer
注解
防止本地运行时端口冲突,修改服务端口为8088
server:
port: 8088
- 注册客户端
- 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}.yaml
,env
通常替代为test
,prod
- 激活指定环境
- 配置文件激活:
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}")
- 命令行参数。
-
配置文件查找位置(为了修改之前不敢动的配置)
- classpath 根路径。
- classpath 根路径下config目录。
- jar包当前目录。
- jar包当前目录的config目录。
- /config子目录的直接子目录。
-
配置文件加载顺序:
- 当前jar包内部的
application.properties
和application.yml
。 - 当前jar包内部的
application-{profile}.properties
和application-{profile}.yml
。 - 引用的外部jar包的
application.properties
和application.yml
。 - 引用的外部jar包的
application-{profile}.properties
和application-{profile}.yml
。
- 当前jar包内部的
-
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。
3. 自定义starter
生产环境中对某个固定功能抽取成一个starter,只需要修改配置文件即可引入
3.1 starter启动原理
- starter的pom.xml引入autoconfigure依赖
-
autoconfigure包中配置使用
META-INF/spring.factories
中EnableAutoConfiguration
的值,使得项目启动加载指定的自动配置类 -
编写自动配置类
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插件,将两工程
clean
—install
到本地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
-
读取一些组件,保存一些信息。
-
判定当前应用的类型。ClassUtils。Servlet
-
bootstrappers:初始启动引导器(List):去spring.factories文件中找org.springframework.boot.Bootstrapper
-
找 ApplicationContextInitializer;去spring.factories找**ApplicationContextInitializer**
- List<ApplicationContextInitializer<?>> initializers
-
找ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
- List<ApplicationListener<?>> listeners
-
-
运行 SpringApplication
-
StopWatch
-
记录应用的启动时间
-
**创建引导上下文(Context环境)**createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
-
让当前应用进入headless模式。java.awt.headless
-
获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
-
遍历SpringApplicationRunListener 调用 starting 方法;
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
-
保存命令行参数;ApplicationArguments
-
准备环境 prepareEnvironment();
-
返回或者创建基础环境信息对象。StandardServletEnvironment
-
配置环境信息对象。
- 读取所有的配置源的配置属性值。
-
绑定环境信息
-
监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
-
-
创建IOC容器(createApplicationContext())
-
根据项目类型(Servlet)创建容器
-
当前会创建 AnnotationConfigServletWebServerApplicationContext
-
-
准备ApplicationContext IOC容器的基本信息 prepareContext()
-
保存环境信息
-
IOC容器的后置处理流程。
-
应用初始化器;applyInitializers;
-
遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
-
遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
-
-
所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
-
-
**刷新IOC容器。**refreshContext
- 创建容器中的所有组件(Spring注解)
-
容器刷新完成后工作?afterRefresh
-
所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
-
**调用所有runners;**callRunners()
-
获取容器中的 ApplicationRunner
-
获取容器中的 CommandLineRunner
-
合并所有runner并且按照@Order进行排序
-
遍历所有的runner。调用 run 方法
-
-
如果以上有异常,
- 调用Listener 的 failed
-
调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
-
**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....");
}
}
- 注册
MyApplicationContextInitializer
,MyApplicationListener
,MySpringApplicationRunListener
:
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