Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
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());
}
}
@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("结束.....");
}
}
断言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,得到完整的测试报告。
不符合断言会报告测试失败,不符合前置条件会被报告测试中止
@DisplayName("前置条件测试")
@Test
void testAssumptions(){
Assumptions.assumeFalse(true,"结果不是True");
}
通过 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());
}
}
}
}
用不同的参数多次运行测试
利用**@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
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
http://localhost:8080/actuator/**
。management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
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 Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到:
1. 开启与禁用Endpoints
management.endpoint..enabled = true
management:
endpoint:
beans:
enabled: true
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
2. 暴露Endpoints
支持的暴露方式
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
Spring-boot-admin开源项目https://github.com/codecentric/spring-boot-admin
作为监控服务器,监控微服务监控指标,可视化展示
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
<version>2.5.1version>
dependency>
@EnableAdminServer
注解防止本地运行时端口冲突,修改服务端口为8088
server:
port: 8088
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-clientartifactId>
<version>2.5.1version>
dependency>
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方式暴露
为了方便多环境(开发环境、生产环境、测试环境)适配切换,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
(修改配置文件的任意值,命令行优先)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;
}
class Color {
}
@Configuration
public class MyConfig {
@Profile("prod")
@Bean
public Color red(){
return new Color();
}
@Profile("test")
@Bean
public Color green(){
return new Color();
}
}
可以激活一组,相当于批量加载prod和test:
spring.profiles.active=production
spring.profiles.group.production[0]=prod
spring.profiles.group.production[1]=test
外部配置源
@Value("${MAVEN_HOME}")
配置文件查找位置(为了修改之前不敢动的配置)
配置文件加载顺序:
application.properties
和application.yml
。application-{profile}.properties
和 application-{profile}.yml
。application.properties
和application.yml
。application-{profile}.properties
和application-{profile}.yml
。指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。
生产环境中对某个固定功能抽取成一个starter,只需要修改配置文件即可引入
autoconfigure包中配置使用META-INF/spring.factories
中EnableAutoConfiguration
的值,使得项目启动加载指定的自动配置类
编写自动配置类 xxxAutoConfiguration
-> xxxxProperties
@Configuration
@Conditional
@EnableConfigurationProperties
@Bean
…
引入starter — xxxAutoConfiguration
— 容器中放入组件 ---- 绑定xxxProperties
---- 配置项
目标:创建
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
依赖:
<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.0modelVersion>
<groupId>com.zjugroupId>
<artifactId>hello-spring-boot-starterartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>com.zjugroupId>
<artifactId>hello-spring-boot-starter-autoconfigureartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
dependencies>
project>
hello-spring-boot-starter-autoconfigure
的pom.xml如下:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.7version>
<relativePath/>
parent>
<groupId>com.zjugroupId>
<artifactId>hello-spring-boot-starter-autoconfigureartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>hello-spring-boot-starter-autoconfigurename>
<description>hello-spring-boot-starter-autoconfiguredescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
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仓库中
clean
—install
到本地maven仓库中4. 实际开发场景中测试使用自定义starter
hello-spring-boot-starter-test
工程,引入hello-spring-boot-starter
依赖:<dependency>
<groupId>com.zjugroupId>
<artifactId>hello-spring-boot-starterartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
application.properties
:hello.prefix=hello
hello.suffix=666
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;
}
}
Spring原理、SpringMVC原理、自动配置原理、SpringBoot原理
创建 SpringApplication
读取一些组件,保存一些信息。
判定当前应用的类型。ClassUtils。Servlet
bootstrappers:初始启动引导器(List):去spring.factories文件中找org.springframework.boot.Bootstrapper
找 ApplicationContextInitializer;去spring.factories找**ApplicationContextInitializer**
找ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
运行 SpringApplication
StopWatch
记录应用的启动时间
**创建引导上下文(Context环境)**createBootstrapContext()
让当前应用进入headless模式。java.awt.headless
获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
遍历SpringApplicationRunListener 调用 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
容器刷新完成后工作?afterRefresh
所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
**调用所有runners;**callRunners()
获取容器中的 ApplicationRunner
获取容器中的 CommandLineRunner
合并所有runner并且按照@Order进行排序
遍历所有的runner。调用 run 方法
如果以上有异常,
调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
**running如果有问题。继续通知 failed 。**调用所有 Listener 的failed;通知所有的监听器 fai
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