SpringBoot简介

​ SpringBoot本质就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置,能迅速的开发web应用。不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。

  • 特点:

(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;

(2)内嵌Tomcat或Jetty等Servlet容器;

(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;

(4)自动配置Spring以及第三方功能;

(5)提供生产级别的监控、健康检查及外部化配置;

(6)无代码生成、无需编写XML;

SpringBoot的使用

SpringBoot的入门使用

一、Maven方式创建
  1. 创建一个maven工程

  2. 导入maven依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <?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>org.example</groupId>
    <artifactId>springBoot_01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--父项目做依赖管理-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>

    <!--开发导入starter场景启动器
    1、见到很多 spring-boot-starter-* : *就某种场景
    2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
    3、SpringBoot所有支持的场景
    https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
    4、见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
    5、所有场景启动器最底层的依赖-->
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    </dependencies>

    <!-- 把项目打成jar包,直接在目标服务器执行即可。-->
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    </project>
  3. 创建主程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.wht.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @SpringBootApplication告诉springboot这是个springboot应用
    */
    @SpringBootApplication
    public class MainApplication {

    public static void main(String[] args) {
    SpringApplication.run(MainApplication.class,args);
    }
    }

  4. 编写业务层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.wht.boot.controller;


    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class HelloController {


    @RequestMapping("/hello")
    public String handler01(){
    return"hello ,spring boot 2!";
    }
    }

  5. 测试

    直接运行main方法

二、通过idea直接创建SpringBoot工程

image-20220718151148104

SpringBoot的自动配置

  • 自动配好Tomcat

  • 引入Tomcat依赖。

    配置Tomcat

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
  • 自动配好SpringMVC

    • 引入SpringMVC全套组件

    • 自动配好SpringMVC常用组件(功能)

  • 自动配好Web常见功能,如:字符编码问题

  • SpringBoot帮我们配置好了所有web开发的常见场景

  • 默认的包结构

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

    • 无需以前的包扫描配置

    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.wht”)

    • 或者@ComponentScan 指定扫描路径

      @SpringBootApplication是合成注解
      等同于
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan(“com.wht.boot”)

  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上,如:MultipartProperties

    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

  • 按需加载所有自动配置项

    • 非常多的starter

    • 引入了哪些场景这个场景的自动配置才会开启

    • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

SpringBoot相关注解

1、@Configuration

  • 基本使用

  • Full模式与Lite模式

    • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断

    • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
*/
@Configuration(proxyBeanMethods = false)
public class MyConfig {
/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

2、@Bean、@Component、@Controller、@Service、@Repository

与spring用法相同

3、@ComponentScan、@Import

1
2
3
4
5
6
7
/* 4、@Import({User.class, DBHelper.class})
* 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
*/
@Import({User.class, DBHelper.class})
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}

4、@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Configuration(proxyBeanMethods = false) 
@ConditionalOnBean(name = "tom")//给类上加,只有当条件满足这个类中的所有bean才会注册到容器中
@ConditionalOnMissingBean(name = "tom")
public class MyConfig {
@Bean
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@ConditionalOnBean(name = "tom")//给方法加,只有当条件满足这个bean才会注册到容器中
@Bean("tom22")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

boolean tom = run.containsBean("tom");
System.out.println("容器中Tom组件:"+tom);

boolean user01 = run.containsBean("user01");
System.out.println("容器中user01组件:"+user01);

boolean tom22 = run.containsBean("tom22");
System.out.println("容器中tom22组件:"+tom22);


}

5、原生配置文件引入 @ImportResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<bean id="haha" class="com.atguigu.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>

<bean id="hehe" class="com.atguigu.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
</beans>



@ImportResource("classpath:beans.xml")
@Configuration(proxyBeanMethods = false)
public class MyConfig {

}

public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}

boolean haha = run.containsBean("haha");
boolean hehe = run.containsBean("hehe");
System.out.println("haha:"+haha);//true
System.out.println("hehe:"+hehe);//true
}

6、配置绑定

1、@Component + @ConfigurationProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
*/
@Component
@ConfigurationProperties(prefix = "mycar")//绑定yml配置文件 prefix为在yml文件中的前缀名
public class Car {

private String brand;
private Integer price;

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}

public Integer getPrice() {
return price;
}

public void setPrice(Integer price) {
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}

2、@EnableConfigurationProperties + @ConfigurationProperties

1
2
3
4
5
@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {
}

7、使用外部properties文件@PropertySource+@value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@PropertySource("classpath:db.properties")
public class AppConfig {

@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.initialSize}")
private int initialSize;

@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driverClassName);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initialSize);
return ds;
}
}

各种插件

Lombok插件

简化javaBean的编写

  1. 依赖:

    1
    2
    3
    4
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @NoArgsConstructor //空参构造
    //@AllArgsConstructor 全参构造
    @Slf4j //日志
    @Data //设置get/set
    @ToString //重写toString
    @EqualsAndHashCode //重写equals和hashCode
    public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
    this.name = name;
    this.age = age;
    log.info("构造函数");
    }
    }

    dev-tool

对项目进行热部署

  1. 依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
  2. 使用

    导入依赖后,每次修改后使用Ctrl+F重新build项目

配置提示插件

将自定义的类的属性在配置文件中进行提示

  1. 依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>
  2. 使用

    属性绑定后,在配置文件中就会有提示

配置文件YAML

  • key: value kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系

  • 缩进不允许使用tab,只允许空格

  • 缩进的空格数不重要,只要相同层级的元素左对齐即可

  • ‘#’表示注释

  • 字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null

    • k:v
  • 对象:键值对的集合。map、hash、set、object

    • 行内写法: k: {k1:v1,k2:v2,k3:v3}
    • k:
      k1: v1
      k2: v2
      k3: v3
      
  • 数组:一组按次序排列的值。array、list、queue

    • 行内写法: k: [v1,v2,v3]
    • k:
      • v1
      • v2
      • v3

多配置文件:

  • 我们在配置文件编写的时候,文件名可以是 application-{profile}.properties/yml用来指定多个环境版本如:
    • application-test.properties 代表测试环境配置
    • application-dev.properties 代表开发环境配置
  • 并在默认application.properties主配置文件中配置:spring.profiles.active=dev

Web开发

1、静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

  • 访问 : 当前项目根路径/ + 静态资源名

  • 原理:请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

  • 改变默认的静态资源路径

    1
    2
    3
    4
    5
    6
    spring:
    mvc:
    static-path-pattern: /res/**

    resources:
    static-locations: [classpath:/haha/]

    2、欢迎页以及错误页面

  • 静态资源路径下 index.html
    ○ 可以配置静态资源路径
    ○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问

  • 错误页面:静态资源路径下创建/error文件夹下4xx.html,5xx.html,springboot会被自动解析(需要有模板引擎thymeleaf等)

3、自定义Favicon

favicon.ico 放在静态资源目录下即可,会自动展示

4、使用Restful风格请求

  • 配置请求方式过滤器:

    1
    2
    3
    4
    5
    spring:
    mvc:
    hiddenmethod:
    filter:
    enabled: true #开启页面表单的Rest功能
  • 注意表单要添加**_method=GET/PUT/POST..**的隐藏input

  • 配置请求映射:@RequestMapping(value = "/user",method = RequestMethod.GET)

5、相关注解

  1. @PathVariable:获取路径参数/car/{id}
  2. @RequestHeader:获取请求头
  3. @RequestParam:获取请求参数
  4. @MatrixVariable:获取矩阵变量如/car/sell;low=34;brand=byd(但需要在webMvcConfigurer进行配置开启)
  5. @CookieValue:获取cookie
  6. @RequestBody:获取请求体的内容
  7. @ResponseBody:可以配合json的三方依赖如:fastJson等来实现直接将响应对象转换为json传给前端
  8. ……同SpringMVC

6、注入web原生组件(Servlet、Filter、Listener)

  • Servlet

    创建servlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyServlet extends HttpServlet {
    // 处理get请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello MyServlet");
    }
    }

    注册servlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public ServletRegistrationBean myServlet() {
    ServletRegistrationBean register = new ServletRegistrationBean(new MyServlet(), "/myServlet");
    // 可以在这里设置相关配置
    register.setLoadOnStartup(1);
    return register;
    }
    }
  • Filter

    创建Filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("MyFilter process...");
    filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {}
    }

    注册Filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    @Bean
    public FilterRegistrationBean myFilter() {
    FilterRegistrationBean register = new FilterRegistrationBean(new MyFilter());
    register.setUrlPatterns(Arrays.asList("/myServlet","/"));
    return register;
    }
    }
  • Listener

    创建Listener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    System.out.println("MyListener.contextInitialized -- web应用启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    System.out.println("MyListener.contextDestroyed -- web应用关闭");
    }
    }

    注册Listener

    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {
    @Bean
    public ServletListenerRegistrationBean myServletContextListener(){
    return new ServletListenerRegistrationBean(new MyServletContextListener());
    }
    }

    拦截器

  1. 创建拦截器(实现HandlerInterceptor 接口):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    @Slf4j
    public class LoginInterceptor implements HandlerInterceptor {

    /**
    * 目标方法执行之前
    * @param request
    * @param response
    * @param handler
    * @return
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String requestURI = request.getRequestURI();
    log.info("preHandle拦截的请求路径是{}",requestURI);

    //登录检查逻辑
    HttpSession session = request.getSession();
    Object loginUser = session.getAttribute("loginUser");
    if(loginUser != null){
    //放行
    return true;
    }

    //拦截住。未登录。跳转到登录页
    request.setAttribute("msg","请先登录");
    //re.sendRedirect("/");重定向
    request.getRequestDispatcher("/").forward(request,response);//请求转发
    return false;
    }

    /**
    * 目标方法执行完成以后
    * @param request
    * @param response
    * @param handler
    * @param modelAndView
    * @throws Exception
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    log.info("postHandle执行{}",modelAndView);
    }

    /**
    * 页面渲染以后
    * @param request
    * @param response
    * @param handler
    * @param ex
    * @throws Exception
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    log.info("afterCompletion执行异常{}",ex);
    }
    }
  2. 在web配置类中添加拦截器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 1、编写一个拦截器实现HandlerInterceptor接口
    * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
    * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
    */
    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") //所有请求都被拦截包括静态资源
    .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
    }

    单元测试

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

如果要继续使用JUnit 4需要导入vintage的依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>

SpringBoot中JUnit 5的使用:

  • 依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
  • 创建测试类:

    现在的Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚

    1
    2
    3
    4
    5
    6
    7
    @SpringBootTest
    class ApplicationTests {
    @Test
    void contextLoads() {

    }
    }

    Junit5的常用注解:

    • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    • @DisplayName :为测试类或者测试方法设置展示名称
    • @BeforeEach :表示在每个单元测试之前执行
    • @AfterEach :表示在每个单元测试之后执行
    • @BeforeAll :表示在所有单元测试之前执行
    • @AfterAll :表示在所有单元测试之后执行
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • @ExtendWith :为测试类或测试方法提供扩展类引用