赞
踩
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</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.<endpointName>.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 |
Spring-boot-admin开源项目https://github.com/codecentric/spring-boot-admin
作为监控服务器,监控微服务监控指标,可视化展示
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.5.1</version>
</dependency>
@EnableAdminServer
注解防止本地运行时端口冲突,修改服务端口为8088
server:
port: 8088
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.5.1</version>
</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
依赖:
<?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仓库中
clean
—install
到本地maven仓库中4. 实际开发场景中测试使用自定义starter
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
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。