0%

Springboot开发实用篇

  • Springboot开发实用篇
    • 配置高级
    • 测试
    • 数据层解决方案

三、开发实用篇

3.1 配置高级

3.1.1 ★属性绑定@ConfigurationProperties

★@Bean
  • application.yml 中的配置信息注入到自定义的 Bean 中:

    1. 自定义 Bean

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      package com.f.springboot.config;

      import lombok.Data;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.stereotype.Component;

      /**
      * @author fzy
      * @date 2024/2/29 19:03
      */
      @Data
      @Component
      @ConfigurationProperties(prefix = "serverconfig")
      public class ServerConfig {
      private String ipAddress;
      private int port;
      private long timeout;
      }
      • @DataLombok 的注解,用于为当前实体类在编译期设置对应的 get/set 方法,toString 方法,hashCode 方法,equals 方法等。
      • @Component 表示将该 Bean 交给 Spring 容器管理。
      • @ConfigurationProperties(prefix = "serverconfig") 表示将配置文件中 servers 相关的配置注入给该 Bean。
    2. application.yml 文件中设置 serverconfig 的相关配置:

      1
      2
      3
      4
      serverconfig:
      ipAddress: 192.168.0.1
      port: 8888
      timeout: -1
    3. 直接在启动类中进行测试:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      package com.f.springboot;

      import com.f.springboot.config.ServerConfig;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ConfigurableApplicationContext;

      @SpringBootApplication
      public class Springboot10ConfigurationApplication {
      public static void main(String[] args) {
      ConfigurableApplicationContext context = SpringApplication.run(Springboot10ConfigurationApplication.class, args);
      ServerConfig serverConfig = context.getBean(ServerConfig.class);
      System.out.println(serverConfig);
      }
      }
      • 测试结果:

        1
        ServerConfig(ipAddress=192.168.0.1, port=8888, timeout=-1)
  • 那该如何application.yml 中的配置信息注入到第三方的 Bean 中呢?

    1. 创建一个方法,返回值是第三方 Bean,在方法体上加上 @Bean@ConfigurationProperties 注解:

      1
      2
      3
      4
      5
      6
      @Bean
      @ConfigurationProperties(prefix = "datasource")
      public DruidDataSource druidDataSource() {
      DruidDataSource druidDataSource = new DruidDataSource();
      return druidDataSource;
      }
      • @Bean 用于将一个方法标记为 Spring 容器中的一个 Bean,具体来说,@Bean 注解可以用于方法上,该方法返回一个对象,该对象将被 Spring 容器管理并提供给其他程序组件使用。
        • 说个题外话:@Configuration 注解作用于类上面,告诉 spring 当前类是作为配置文件使用的,相当于 spring 中的 xml 配置文件。
          • @Bean@Configuration 注解一起使用的话,等价于在 spring.xml 配置文件中注册了 Bean。
    2. application.yml 文件中设置 datasource 的相关配置:

      1
      2
      datasource:
      driverClassName: com.mysql.cj.jdbc.Driver111
    3. 直接在启动类中进行测试:

      1
      2
      3
      4
      5
      6
      7
      public static void main(String[] args) {
      ConfigurableApplicationContext context = SpringApplication.run(Springboot10ConfigurationApplication.class, args);
      //ServerConfig serverConfig = context.getBean(ServerConfig.class);
      //System.out.println(serverConfig);
      DruidDataSource druidDataSource = context.getBean(DruidDataSource.class);
      System.out.println(druidDataSource.getDriverClassName());
      }
      • 测试结果:

        1
        com.mysql.cj.jdbc.Driver111
宽松绑定(松散绑定)
  • @ConfigurationProperties 绑定属性支持属性名宽松绑定,又叫松散绑定。

    • 比如要将 ServerConfig.class 作为配置类,并通过配置文件 application.yml 绑定属性:

      • ServerConfig.class 的定义和上面一样:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        package com.f.springboot.config;

        import lombok.Data;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import org.springframework.stereotype.Component;

        /**
        * @author fzy
        * @date 2024/2/29 19:03
        */
        @Data
        @Component
        @ConfigurationProperties(prefix = "serverconfig")
        public class ServerConfig {
        private String ipAddress;
        private int port;
        private long timeout;
        }
      • 然后配置文件:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        #server-config:
        serverconfig:
        # ipAddress: 192.168.0.1 # 驼峰模式
        # ipaddress: 192.168.0.1
        # IPADDRESS: 192.168.0.1
        ip-address: 192.168.0.1 # 主流配置方式,烤肉串模式
        # ip_address: 192.168.0.1 # 下划线模式
        # IP_ADDRESS: 192.168.0.1 # 常量模式
        # ip_Add_rEss: 192.168.0.1
        # ipaddress: 192.168.0.1
        port: 8888
        timeout: -1
        • ipAddress 属性为例,上面的多种配置方式皆可生效,这就是松散绑定。

          @Value 不支持松散绑定,必须一一对应。

  • @ConfigurationProperties(prefix="serverconfig") 中的 prefix 的值为 serverconfig 或者 server-config 都可以,如果是 serverConfig 就会报错,这与松散绑定的前缀命名规范有关:仅能使用纯小写字母、数字、中划线作为合法的字符

3.1.2 常用计量单位应用

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
package com.f.springboot.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.stereotype.Component;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

import java.time.Duration;
import java.time.temporal.ChronoUnit;

/**
* @author fzy
* @date 2024/2/29 19:03
*/
@Data
@Component
@ConfigurationProperties(prefix = "serverconfig")
public class ServerConfig {
private String ipAddress;
private int port;
private long timeout;
@DurationUnit(ChronoUnit.HOURS)
private Duration serverTimeout;
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize dataSize;
}
1
2
3
4
5
6
7
#server-config:
serverconfig:
ipAddress: 192.168.0.1
port: 8888
timeout: -1
serverTimeout: 3
dataSize: 10
1
2
3
4
5
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Springboot10ConfigurationApplication.class, args);
ServerConfig serverConfig = context.getBean(ServerConfig.class);
System.out.println(serverConfig);
}
  • 结果:

    1
    ServerConfig(ipAddress=192.168.0.1, port=8888, timeout=-1, serverTimeout=PT3H, dataSize=10485760B)
    • @DurationUnit(ChronoUnit.HOURS) 用于指定 serverTimeout 的时间单位为小时。
    • @DataSizeUnit(DataUnit.MEGABYTES) 用于指定 dataSize 的存储单位为 MB。

3.2 测试

3.2.1 加载测试专用属性

  • @SpringBootTest 注解中可以设置 propertiesargs 属性,这里的 args 属性的作用跟 idea 工具中自带的程序参数类似,只不过这里的配置是源码级别的,会随着源码的移动而跟随,而 idea 中的程序参数的配置会丢失。并且这里的 args 属性的配置的作用范围比较小,仅在当前测试类生效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.f.springboot;

    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.context.SpringBootTest;

    /**
    * @author fzy
    * @date 2024/3/2 12:51
    */
    // properties属性可以为当前测试用例添加临时的属性配置
    //@SpringBootTest(properties = {"test.prop=testValue_properties"})
    // args属性可以为当前测试用例添加临时的命令行参数
    //@SpringBootTest(args = {"--test.prop=testValue_args"})
    @SpringBootTest(properties = {"test.prop=testValue_properties"}, args = {"--test.prop=testValue_args"})
    public class PropertiesAndArgsTest {
    @Value("${test.prop}")
    private String msg;

    @Test
    public void testPropertiesAndArgs() {
    System.out.println(msg);
    }
    }
    • 输出:testValue_properties

    • 如果此时,application.yml 文件中也配置了 test.prop 属性,那输出会是什么呢?

      1
      2
      test:
      prop: testValue
      • 输出:testValue_properties

      优先级排序: properties > args > 配置文件

3.2.2 加载测试专用Bean

  • 某些测试类中需要使用第三方类的 Bean,而其他测试类又不需要使用,也就是说该 Bean 只被该测试类使用,这时我们就可以加载测试专用的 Bean,通过使用 @Import({xxx.class, yyy.class}) 来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 第三方类的Bean
    package com.f.springboot.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * @author fzy
    * @date 2024/3/2 13:11
    */
    @Configuration
    public class MsgConfig {
    @Bean
    public String msg() {
    return "Bean msg...";
    }
    }
    • 因为是测试专用配置,所以并不创建在 src 目录下,而是创建在 test 目录下。
    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
    // 测试类
    package com.f.springboot;

    import com.f.springboot.config.MsgConfig;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.context.annotation.Import;

    /**
    * @author fzy
    * @date 2024/3/2 13:12
    */
    @SpringBootTest
    // 使用@Import注解加载测试专用配置
    @Import({MsgConfig.class})
    public class ConfigurationTest {
    @Autowired
    private String msg;

    @Test
    public void testConfiguration() {
    System.out.println(msg);
    }
    }
    • 输出:Bean msg...
  • 通过加载测试范围的配置,应用于小范围测试环境。

3.2.3 测试表现层

  • 1.5 ★★★基于Springboot的SSMP整合 小节中,我们只对数据访问层和业务逻辑层的功能进行了测试,而对表现层的测试是通过 postman 实现的,但是使用 maven 打包项目的时候,应该对三层都要进行测试。接下来展示如何在 IDEA 中对表现层进行测试。
3.2.3.1 测试类中启动web环境
  • @SpringbootTest 注解中有一个属性 webEnvironment,用于在测试类中启动 web 环境:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

    • DEFINED_PORT 表示用定义的端口启动 web 环境。
      • 定义的端口可以写在 application.yml 中,也可以写在测试专用属性 properties 或者 args 中。
    • RANDOM_PORT 表示用随机端口启动 web 环境。
3.2.3.2 发送虚拟请求
  • 在测试类中发送虚拟请求有以下几步:

    1. 使用 @AutoConfigureMockMvc 注解开启 mvc 虚拟调用。
    2. 发送虚拟请求,需要有一个虚拟调用对象 MockMvc,通过定义并自动注入来得到。
    3. 创建 RequestBuilder 对象,用于发送虚拟请求。
    4. 虚拟调用对象 MockMvc 执行虚拟请求 RequestBuilder
    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
    package com.f.springboot.web;

    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.RequestBuilder;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

    /**
    * @author fzy
    * @date 2024/3/2 13:23
    */
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    // 1.开启虚拟mvc调用
    @AutoConfigureMockMvc
    public class WebTest {
    // 2.虚拟调用对象
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void tesRandomPort() {
    }

    @Test
    public void testWeb() throws Exception {
    // 3.创建RequestBuilder对象,用于发送虚拟请求(GET)
    RequestBuilder builder = MockMvcRequestBuilders.get("/web");
    //RequestBuilder builder = MockMvcRequestBuilders.post("/web");
    //RequestBuilder builder = MockMvcRequestBuilders.put("/web");
    //RequestBuilder builder = MockMvcRequestBuilders.delete("/web");
    // 4.虚拟调用对象执行虚拟请求
    mockMvc.perform(builder);
    }
    }
3.2.3.3 匹配响应
  • 判断测试是否通过:

    • 设定预期值,与测试值进行比较,相等则测试通过,否则测试失败。
  • 匹配响应状态:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void testStatus() throws Exception {
    RequestBuilder builder = MockMvcRequestBuilders.get("/web");
    ResultActions action = mockMvc.perform(builder);
    // 判断测试是否通过:
    // 设定预期值,与测试值进行比较,相等则测试通过,否则测试失败
    // 定义执行状态匹配器
    StatusResultMatchers status = MockMvcResultMatchers.status();
    // 定义预期执行状态,这里定义为成功响应
    ResultMatcher ok = status.isOk();
    // 添加预计值到本次调用过程中,进行匹配
    action.andExpect(ok);
    }
  • 匹配响应体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test
    public void testBody() throws Exception {
    RequestBuilder builder = MockMvcRequestBuilders.get("/web");
    ResultActions action = mockMvc.perform(builder);
    // 定义执行结果匹配器
    ContentResultMatchers content = MockMvcResultMatchers.content();
    // 定义预期执行结果
    ResultMatcher result = content.string("springboot");
    // 添加预计值到本次调用过程中,进行匹配
    action.andExpect(result);
    }
    • 一般后端返回结果都是 json 类型的,所以对上面的代码进行改造:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Test
      public void testJsonBody() throws Exception {
      RequestBuilder builder = MockMvcRequestBuilders.get("/web");
      ResultActions action = mockMvc.perform(builder);
      // 定义执行结果匹配器
      ContentResultMatchers content = MockMvcResultMatchers.content();
      // 定义预期执行结果(json格式)
      String jsonBody = "{\n" +
      " \"domain\": \"www.baidu.com\",\n" +
      " \"content\": \"搜索网址\"\n" +
      "}";
      ResultMatcher result = content.json(jsonBody);
      // 添加预计值到本次调用过程中,进行匹配
      action.andExpect(result);
      }

3.2.4 业务层测试进行事务回滚

  • 2.1.1 程序打包 小节提到过,对数据访问层和业务逻辑层的功能进行测试时,会对数据库的数据产生影响。

    所以我们需要为测试用例添加事务,SpringBoot 就会对测试用例对应的事务提交操作进行回滚,也就避免测试对数据库的数据产生影响。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootTest
    // 开启事务
    @Transactional
    public class DaoTest {
    @Autowired
    private BookService bookService;
    // 测试方法
    ......
    }
    • 如果想在测试用例中提交事务,可以通过 @Rollback(false) 注解设置:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      @SpringBootTest
      // 开启事务
      @Transactional
      // 不回滚
      @Rollback(false)
      public class DaoTest {
      @Autowired
      private BookService bookService;
      // 测试方法
      ......
      }

3.2.5 测试用例设置随机数据

  • 测试用例数据通常采用随机值进行测试,我们可以使用 SpringBoot 提供的随机数为其赋值。

    • application.yml 文件中可以设置随机值:

      1
      2
      3
      4
      5
      6
      7
      8
      testcast:
      book:
      id: ${random.int} # 随机整数
      id2: ${random.int(10)} # 10以内随机数
      type: ${random.int(10,20)} # 10到20随机数
      uuid: ${random.uuid} # 随机uuid
      name: ${random.value} # 随机字符串,MD5字符串,32位
      publishTime: ${random.long} # 随机整数(long范围)
      • 然后使用 @ConfigurationProperties(prefix = "testcase.book") 注解将数据绑定到测试实体类上即可。

3.3 数据层解决方案

  • 目前我们数据层解决方案的技术选型为:Druid + MyBatis-Plus + MySQL

    • 数据源:DruidDataSource
    • 持久化技术:MyBatis-Plus / MyBatis
    • 数据库:MySQL
  • 数据源:

    • SpringBoot 实际上提供了 3 种内嵌的数据源对象供开发者选择:
      • HikariCP(不使用任何第三方数据源的话,默认是这个)
      • Tomcat 提供的 DataSource:HikariCP 不可用的情况下,且在 web 环境中,将使用 tomcat 服务器配置的数据源对象
      • Commons DBCP:Hikari 不可用,tomcat 数据源也不可用,将使用 dbcp 数据源
  • 持久化技术:

    • Springboot 内置的持久化解决方案 —— JdbcTemplate
  • 数据库:

    • SpringBoot 提供了 3 种内嵌数据库供开发者选择,以提高开发测试效率(因为它们都是 Java 语言写的,是内存级数据库):
      • H2
      • HSQL
      • Derby
---------------The End---------------