springboot map 接收配置文件 springboot接受map_学习


文章目录

  • 前言
  • 一、pom依赖
  • 二、简单使用
  • 2.1 转换类型
  • 2.1.1 Bean -> Bean
  • 2.1.2 List -> List, Collection->Collection
  • 2.1.3 Map -> Bean
  • 2.1.4 Streams -> Collection
  • 2.1.5 Enum -> Integer
  • 2.2 更新Bean
  • 2.3 类型转化
  • 2.3.1 数字
  • 2.3.2 时间
  • 2.4 集成spring
  • 2.5 复用mapper
  • 三、复杂用法
  • 3.1 组合
  • 3.2 回调
  • 3.3 表达式
  • 3.4 选择填充
  • 参考


前言

Java bean映射框架有很多,在之前我已经有一篇博文介绍了dozer, 它也是一个优秀的映射框架,但是作者已经不再维护了,不过作者在readme里推荐了另一个类似的框架 mapstruct ,所以准备开始学习它。

根据官网介绍,mapstruct 是一个Java注解处理器,用于为Java bean类生成类型安全和性能良好的映射类。使用 mapstruct ,只需要定义一个 Mapper 接口,声明需要映射的方法,在编译过程中,mapstruct 会自动生成该接口的实现类,实现将源对象映射到目标对象。

mapstruct 是在编译期动态生成getter/setter,而 dozer 是在运行期间使用反射,这是它俩最大的不同。dozer 最大的缺点就是性能不好,这与反射密切相关,而mapstruct是在编译时动态生成,这表示它的速度更快,而且 mapstruct 还支持不同名属性映射,这是 dozer比其它几个工具:Cglib 的 BeanCopier 、Apache 的 PropertyUtils 、Spring 的 BeanUtils 最大的优势。综上所述:看来使用 mapstruct 替代 dozer 更有未来。

mapstruct 是基于 JSR 269 实现的,JSR 269 是 JDK 引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。


一、pom依赖

<?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>
    <groupId>com.aabond</groupId>
    <artifactId>demomapstruct</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demomapstruct</name>
    <description>demomapstruct</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <org.projectlombok.version>1.18.16</org.projectlombok.version>
        <org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding</artifactId>
                            <version>${lombok-mapstruct-binding.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.aabond.demomapstruct.DemomapstructApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

二、简单使用

2.1 转换类型

2.1.1 Bean -> Bean

mapstruct 支持单个 bean 映射到另一个 bean ,默认同名映射,不同名使用 @Mapping 进行配置,不想进行映射的字段可以使用 ignore = true 来配置不参与映射。双向映射可以使用 @InheritInverseConfiguration 来简化配置

注意:使用 @Mapping 时,target 是要生成的对象属性,必填。而 source 是参数对象属性,选填

@Data
public class User {
    private Long id;
    private String username;
    private String pawword;
}
@Data
public class UserDto {
    private Long userId;
    private String username;
    private String pawword;
}
@Mapper
public interface UserMapper {

    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "userId", source = "id")
    @Mapping(target = "pawword", ignore = true)
    UserDto userToUserDto(User user);
    
    @InheritInverseConfiguration(name = "userToUserDto")
    User userDtoToUser(UserDto userDto);
}
private static final Logger logger = LoggerFactory.getLogger(UserMapperTest.class);

@Test
void userToUserDtoTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413bvw2-");

    UserDto userDto = UserMapper.INSTANCT.userToUserDto(user);
    
    logger.info("{}", userDto);
    
	assertThat(userDto.getUserId(), is(user.getId()));
	assertThat(userDto.getUsername(), is(user.getUsername()));
	assertThat(userDto.getPassword(), is(nullValue()));
}

@Test
void userDtoToUserTest() {
    UserDto userDto = new UserDto();
    userDto.setUserId(1L);
    userDto.setUsername("王二");
    userDto.setPassword("zzzzzzz");

    User user = UserMapper.INSTANCT.userDtoToUser(userDto);
    logger.info("{}", user);
    assertThat(user.getPawword(), is(nullValue()));
    assertThat(user.getId(), is(userDto.getUserId()));
    assertThat(user.getUsername(), is(userDto.getUsername()));
}

2.1.2 List -> List, Collection->Collection

在UserMapper增加两个方法,通过查看target下生成代码,可以看出实际还是调用userToUserDto,再组装成list或set

@Mapper
public interface UserMapper {

    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "userId", source = "id")
    UserDto userToUserDto(User user);
    
    List<UserDto> userListToUserDtoList(List<User> list);
	Set<UserDto> userListToUserDtoSet(List<User> list);
}
@Test
void userListToUserDtoListTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");

    User user1 = new User();
    user1.setId(19L);
    user1.setUsername("李四");
    user1.setPawword("vewrwwrw1231ev>/we");

    UserDto userDto = UserMapper.INSTANCT.userToUserDto(user);
    logger.info("{}", userDto);

    List<User> userList = new ArrayList<>();
    userList.add(user);
    userList.add(user1);
    userList.add(user);
        
    List<UserDto> userDtos = UserMapper.INSTANCT.userListToUserDtoList(userList);
    Set<UserDto> userDtoSet = UserMapper.INSTANCT.userListToUserDtoSet(userList);
    logger.info("{}", userDtos);
    logger.info("{}", userDtoSet);
    
    assertThat(userDtos, is(hasSize(3)));
    assertThat(userDtoSet, is(hasSize(2)));
}

2.1.3 Map -> Bean

mapstruct v1.5 支持了Map<String,?>bean的转换,可以将一个 map 转为 bean ,简单的对象属性可以直接使用 Map<String, String> ,mapstruct 默认会将 String 转成Integer ,Long 等原生属性。但是如果是复杂转化,则需要自己定义转换规则。

@Mapper
public interface UserMapper {

    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);
    UserDto convertFromMap(Map<String, String> map);
}
@Test
void convertFromMap() {
    Map<String, String> map = new HashMap<>();
    map.put("userId", "8");
    map.put("username", "王五");
    UserDto userDto = UserMapper.INSTANCT.convertFromMap(map);
    logger.info("{}", userDto);
    
    assertThat(userDto.getUserId(), is(8L));
    assertThat(userDto.getUsername(), is("王五"));
}

2.1.4 Streams -> Collection

List<UserDto> convertStream(Stream<User> user);
@Test
void convertStreamTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");

    User user1 = new User();
    user1.setId(19L);
    user1.setUsername("李四");
    user1.setPawword("vewrwwrw1231ev>/we");

    List<UserDto> userDtos = UserMapper.INSTANCT.convertStream(Stream.of(user, user1));
    assertThat(userDtos, is(hasSize(2)));
}

2.1.5 Enum -> Integer

支持枚举类型与Integer相互转化,需要提供对应的方法

public enum UserTypeEnum {
    NORMAL(0), ADMIN(1), SUPER_ADMIN(2);
    private Integer value;

    UserTypeEnum(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }
    
    public static UserTypeEnum getEnumByValue(Integer value) {
    	return Arrays.stream(UserTypeEnum.values())
            .filter(enumValue -> enumValue.getValue().equals(value))
            .findFirst()
            .orElse(null);
	}
}
@Mapping(target = "userId", source = "id")
@Mapping(target = "userType", source = "userTypeEnum")
UserDto userToUserDto(User user);

@InheritInverseConfiguration(name = "userToUserDto")
User userDtoToUser(UserDto userDto);

default UserTypeEnum IntegerToUserTypeEnum(int value) {
    return UserTypeEnum.getEnumByValue(value);
}

default Integer UserTypeEnumToInteger(UserTypeEnum userTypeEnum) {
    return userTypeEnum.getValue();
}
@Data
public class User {
    private Long id;
    private String username;
    private String pawword;
    private UserTypeEnum userTypeEnum;
}
@Data
public class UserDto {
    private Long userId;
    private String username;
    private Integer userType;
}
@Test
void userToUserDtoEnumTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");
    user.setUserTypeEnum(UserTypeEnum.ADMIN);

    UserDto userDto = UserMapper.INSTANCT.userToUserDto(user);
    logger.info("{}", userDto);

    assertThat(userDto.getUserType(), is(UserTypeEnum.ADMIN.getValue()));
}

2.2 更新Bean

有时候是想更新bean,而不是生成bean, 下面以根据 user 更新 userDto 为例

@InheritConfiguration 可以继承配置

@InheritConfiguration
void updateUserDto(User user, @MappingTarget UserDto userDto);
@Test
void updateUserDtoTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");

    UserDto userDto = new UserDto();
    userDto.setUserId(100L);

    UserMapper.INSTANCT.updateUserDto(user, userDto);
    logger.info("{}", userDto);
    assertThat(userDto.getUserId(), is(12L));
}

2.3 类型转化

@Data
public class Car {
    private String make;
    private int numberOfSeats;
    private String price;
    private LocalDate sellDate;
}
@Data
public class CarDto {
    private String make;
    private int seatCount;
    private String type;
    private Double price;
    private String sellDate;
}

2.3.1 数字

数字类型、字符串相互转换,字符串与数字之间通过java.text.DecimalFormat转化

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(target = "price", numberFormat = "#.00")
    CarDto carToCarDto(Car car);

    @IterableMapping(numberFormat = "#.00")
    List<String> prices(List<Integer> prices);
}
@Test
void carToCarDtoTest() {
    Car car = new Car();
    car.setPrice("123400000");
    car.setMake("五菱");
    car.setNumberOfSeats(6);

    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    logger.info("{}", carDto);

    assertThat(carDto.getPrice(), is(123400000D));
}

@Test
void pricesTest() {
    List<Integer> prices = new ArrayList<>();
    prices.add(12);
    prices.add(34);
    prices.add(340);
    List<String> list = CarMapper.INSTANCE.prices(prices);

    logger.info("{}", list);
    assertThat(list, is(Arrays.asList("12.00", "34.00", "340.00")));
}

2.3.2 时间

@Mapping(target = "sellDate", dateFormat = "dd.MM.yyyy")
CarDto carToCarDto(Car car);

@IterableMapping(dateFormat = "yyyy-MM-dd")
List<String> stringListToDateList(List<LocalDate> dates);
@Test
void carToCarDtoTest() {
    Car car = new Car();
    car.setPrice("123400000");
    car.setMake("五菱");
    car.setNumberOfSeats(6);
    car.setSellDate(LocalDate.of(2022, 1, 1));

    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    logger.info("{}", carDto);

    assertThat(carDto.getSellDate(), is("01.01.2022"));
}

@Test
void stringListToDateListTest() {
    List<LocalDate> dates = Arrays.asList(LocalDate.of(1999, 1, 1), LocalDate.of(2022, 12, 12));
    List<String> strings = CarMapper.INSTANCE.stringListToDateList(dates);
    logger.info("{}", strings);
    assertThat(strings, is(Arrays.asList("1999-01-01", "2022-12-12")));
}

2.4 集成spring

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface CarMapper {
    @IterableMapping(numberFormat = "#.00")
    List<String> prices(List<Integer> prices);
}
@Autowired
private CarMapper carMapper;

@Test
void pricesSpringTest() {
    List<Integer> prices = new ArrayList<>();
    prices.add(12);
    prices.add(34);
    prices.add(340);
    List<String> list = carMapper.prices(prices);

    logger.info("{}", list);
    assertThat(list, is(Arrays.asList("12.00", "34.00", "340.00")));
}

2.5 复用mapper

mapstruct提供复用功能,可以将其他mapper或手写转化方法的类,使用uses可被调用

@Data
public class User {
    private Long id;
    private String username;
    private String pawword;
    private LocalDate birthdate;

}
@Data
public class UserDto {
    private Long userId;
    private String username;
	private String birthdate;
}
@Mapper(uses = {DateMapper.class})
public interface UserMapper{

    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "userId", source = "id")
    UserDto userToUserDto(User user);
}
public class DateMapper {

    private static final String DATE_PATTERN = "yyyy-MM-dd";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);

    public String asString(LocalDate date) {
        return date != null ? date.format(DATE_FORMATTER): null;
    }

    public LocalDate asDate(String date) {
        return LocalDate.parse(date, DATE_FORMATTER);
    }
}
void userToUserVo() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");
    user.setBirthdate(LocalDate.of(1999, 1, 1));

    UserDto userDto = UserMapper.INSTANCT.userToUserDto(user);
    logger.info("{}", userDto);
    
    assertThat(userDto.getBirthdate(), is("1999-01-01"));
}

三、复杂用法

3.1 组合

将多个Bean合并到一个Dto中

@Data
public class UserInfoDto {
    private Integer userId;
    private String userName;
    private String make;
    private Integer num;
}
@Mapping(target = "userId", source = "user.id")
@Mapping(target = "userName", source = "user.username")
@Mapping(target = "make", source = "car.make")
UserInfoDto toUserInfoDto(User user, Car car, String num);
@Test
void toUserInfoDtoTest() {
    User user = new User();
    user.setId(12L);
    user.setUsername("张三");
    user.setPawword("zedbw413 bvw2-");
    user.setUserTypeEnum(UserTypeEnum.ADMIN);

    Car car = new Car();
    car.setPrice("123400000");
    car.setMake("五菱");
    car.setNumberOfSeats(6);
    car.setSellDate(LocalDate.of(2022, 1, 1));

    UserInfoDto userInfoDto = UserMapper.INSTANCT.toUserInfoDto(user, car, "2");
    logger.info("{}", userInfoDto);

    assertThat(userInfoDto.getUserId(), is(user.getId().intValue()));
    assertThat(userInfoDto.getUserName(), is(user.getUsername()));
    assertThat(userInfoDto.getMake(), is(car.getMake()));
    assertThat(userInfoDto.getNum(), is(2));
}

3.2 回调

mapstruct 提供两个注解 @BeforeMapping 和 @AfterMapping ,用于回调,

@BeforeMapping
default void before(Car car) {
    car.setMake(car.getMake() + " + before");
}

@AfterMapping
default void after(@MappingTarget CarDto carDto) {
    carDto.setMake(carDto.getMake() + " + after");
}
@Test
void beforeAfterTest() {
    Car car = new Car();
    car.setPrice("123400000");
    car.setMake("五菱");
    car.setNumberOfSeats(6);
    car.setSellDate(LocalDate.of(2022, 1, 1));

    CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    logger.info("{}", carDto);

    assertThat(carDto.getMake(), is("五菱 + before + after"));
}

3.3 表达式

mapstruct 支持通过表达式表示对属性进行填充,暂时只支持java

@Data
public class CarDto {
    private String make;
    private int seatCount;
    private String type;
    private Double price;
    private String sellDate;

    private List<String> list;
}
import java.util.Arrays;
@Mapper(imports = Arrays.class)
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);


    @Mapping(target = "list", expression = "java(Arrays.asList(name, car.getMake(), car.getPrice()))")
    CarDto experession(Car car, String name);
}
@Test
void experessionTest() {
    Car car = new Car();
    car.setPrice("123400000");
    car.setMake("五菱");
    car.setNumberOfSeats(6);
    car.setSellDate(LocalDate.of(2022, 1, 1));

    CarDto test = CarMapper.INSTANCE.experession(car, "test");
    logger.info("{}", test);
    List<String> list = new ArrayList<>();
    list.add("test");
    list.add(car.getMake());
    list.add(car.getPrice());
    assertThat(test.getList(), is(list));
}

3.4 选择填充

mapstruct 支持 用户编写自定义条件方法,返回值需为boolean, 这些方法将被调用以检查是否需要映射属性

@Mapper
public interface CarMapper {

    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
    @Mapping(target = "price", numberFormat = "#.00")
    @Mapping(target = "sellDate", dateFormat = "yyyy.MM.dd")
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
    
    @Condition
    default boolean isNotEmpty(String value) {
        return value != null && !value.isEmpty();
    }
}
@Test
void testCondition() {
    Car car = new Car();
    car.setPrice("");
    car.setMake("");
    car.setNumberOfSeats(6);
    car.setSellDate(LocalDate.of(2022, 1, 1));

    CarDto carDto = carMapper.carToCarDto(car);

    assertThat(carDto.getMake(), is(nullValue()));
    assertThat(carDto.getPrice(), is(nullValue()));
}