乐趣区

关于java:彻底干掉-BeanUtils最优雅的-Mapstruct-增强工具全新出炉

背景

在当初风行的零碎设计中,个别会将对象模型划分为多个档次,例如 VO、DTO、PO、BO 等等。这同时也产生了一个问题,常常须要进行不同层级的模型之间互相转换。

针对这种问题,目前常会采纳三种计划:

  1. 调用每个字段的 getter/setter 进行赋值。这个过程,干燥且乏味,容易出错的同时,极易容易造成代码行数迅速收缩,可浏览性差。
  2. apache-commons、Spring 等提供的 BeanUtil 工具类,这种工具类应用十分不便,一行代码即可实现映射。但其外部采纳反射的形式来实现映射,性能低下,呈现问题时,调试艰难,当须要个性化转换时,配置麻烦,十分不倡议应用,特地是对于性能要求比拟高的程序中。
  3. mapstruct:这个框架是基于 Java 正文处理器,定义一个转换接口,在编译的时候,会依据接口类和办法相干的注解,主动生成转换实现类。生成的转换逻辑,是基于 getter/setter 办法的,所以不会像 BeanUtil 等耗费其性能。

下面的三种办法中,最优良的莫属 mapstruct 了,当然,美中不足的就是,当零碎较为简单,对象较多且结构复杂,又或者有的我的项目设计中会定义多层对象模型(如 DDD 畛域设计),须要定义较多的转换接口和转换方法,这也是一些开发者放弃 Mapstruct 的次要起因。

这里,就要给大家介绍一个 Mapstruct 的加强包 —— Mapstruct Plus,一个注解,能够生成两个类之间的转换接口,使 Java 类型转换更加便捷、优雅,彻底摈弃 BeanUtils。

疾速开始

上面演示如何应用 MapStruct Plus 来映射两个对象。

假如有两个类 UserDtoUser,别离示意数据层对象和业务层对象:

  • UserDto
public class UserDto {
    private String username;
    private int age;
    private boolean young;

    // getter、setter、toString、equals、hashCode
}
  • User
public class User {
    private String username;
    private int age;
    private boolean young;

    // getter、setter、toString、equals、hashCode
}

增加依赖

引入 mapstruct-plus-spring-boot-starter 依赖:

<properties>
    <mapstruct-plus.version>1.1.3</mapstruct-plus.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
</dependencies>
<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>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.github.linpeilie</groupId>
                        <artifactId>mapstruct-plus-processor</artifactId>
                        <version>${mapstruct-plus.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

指定对象映射关系

User 或者 UserDto 下面减少注解 —— @AutoMapper,并设置 targetType 为对方类。

例如:

@AutoMapper(target = UserDto.class)
public class User {// ...}

测试

@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void test() {User user = new User();
        user.setUsername("jack");
        user.setAge(23);
        user.setYoung(false);

        UserDto userDto = converter.convert(user, UserDto.class);
        System.out.println(userDto);    // UserDto{username='jack', age=23, young=false}

        assert user.getUsername().equals(userDto.getUsername());
        assert user.getAge() == userDto.getAge();
        assert user.isYoung() == userDto.isYoung();

        User newUser = converter.convert(userDto, User.class);

        System.out.println(newUser);    // User{username='jack', age=23, young=false}

        assert user.getUsername().equals(newUser.getUsername());
        assert user.getAge() == newUser.getAge();
        assert user.isYoung() == newUser.isYoung();
    }

}

小结

引入依赖后,应用 Mapstruct Plus 步骤非常简单。

  1. 给须要转换的类增加 AutoMapper 注解
  2. 获取 Converter 实例,调用 convert 办法即可

个性

齐全兼容 Mapstruct

Mapst 实现了加强操作,如果之前曾经应用了 Mapstruct,能够间接替换相干依赖。

单注解即可实现两个对象互相转换

例如疾速开始中,只在 User 类下面减少注解 @AutoMapper,Mapstruct Plus 除了会生成 User -> UserDto 的转换接口,默认还会生成 UserDto -> User 的转换接口。

编译后,能够查看生成的类,如下:

自定义对象类型的属性主动转换

当两个须要转换的对象 AADto,其中的属性 BBDto 是自定义类型,并且也定义了 @AutoMapper 注解的话,在生成转换 AADto 时,会主动依赖属性 BBDto 的转换接口,实现其相应的属性转换。

例如:
别离有两组对象模型:汽车(Car) 和座椅配置(SeatConfiguration),其中 Car 依赖 SeatConfiguration。

两组对象构造统一,只不过代表层级不一样,这里拿 dto 层的对象定义举例:

  • CarDto
@AutoMapper(target = Car.class)
public class CarDto {private SeatConfigurationDto seatConfiguration;}
  • SeatConfiguration
@AutoMapper(target = SeatConfiguration.class)
public class SeatConfigurationDto {private int seatCount;}

测试:

@Test
public void carConvertTest() {CarDto carDto = new CarDto();
    SeatConfigurationDto seatConfigurationDto = new SeatConfigurationDto();
    seatConfigurationDto.setSeatCount(4);
    carDto.setSeatConfiguration(seatConfigurationDto);

    final Car car = converter.convert(carDto, Car.class);
    System.out.println(car);    // Car(seatConfiguration=SeatConfiguration(seatCount=4))

    assert car.getSeatConfiguration() != null;
    assert car.getSeatConfiguration().getSeatCount() == 4;
}

单个对象对多个对象进行转换

Mapstruct Plus 提供了 @AutoMappers 注解,反对配置多个指标对象,进行转换。

例如:有三个对象,UserUserDtoUserVOUser 别离要和其余两个对象进行转换。则能够按如下配置:

@Data
@AutoMappers({@AutoMapper(target = UserDto.class),
    @AutoMapper(target = UserVO.class)
})
public class User {// ...}

Map 转对象

Mapstruct Plus 提供了 @AutoMapMapper 注解,反对生成 Map<String, Object> 转换为以后类的接口。同时,还反对 map 中嵌套 Map<String, Object> 转换为自定义类嵌套自定义类的场景。

其中,map 中的 value 反对的类型如下:

  • String
  • BigDecimal
  • BigInteger
  • Integer
  • Long
  • Double
  • Number
  • Boolean
  • Date
  • LocalDateTime
  • LocalDate
  • LocalTime
  • URI
  • URL
  • Calendar
  • Currency
  • 自定义类(自定义类也须要减少 @AutoMapMapper 注解

例如:

有如下两个接口:

  • MapModelA
@Data
@AutoMapMapper
public class MapModelA {

    private String str;
    private int i1;
    private Long l2;
    private MapModelB mapModelB;

}
  • MapModelB
@Data
@AutoMapMapper
public class MapModelB {private Date date;}

测试:

@Test
public void test() {Map<String, Object> mapModel1 = new HashMap<>();
    mapModel1.put("str", "1jkf1ijkj3f");
    mapModel1.put("i1", 111);
    mapModel1.put("l2", 11231);

    Map<String, Object> mapModel2 = new HashMap<>();
    mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23"));

    mapModel1.put("mapModelB", mapModel2);

    final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class);
    System.out.println(mapModelA);  // MapModelA(str=1jkf1ijkj3f, i1=111, l2=11231, mapModelB=MapModelB(date=2023-02-23 01:03:23))
}

反对自定义转换

自定义属性转换

Mapstruct Plus 提供了 @AutoMapping 注解,该注解在编译后,会变为 Mapstruct 中的 @Mapping 注解,曾经实现了几个罕用的注解属性。

指定不同名称属性字段映射转换

例如,Car 类中属性 wheels 属性,转换 CarDto 类型时,须要将该字段映射到 wheelList 下面,能够在 wheels 下面减少如下注解:

@AutoMapper(target = CarDto.class)
public class Car {@AutoMapping(target = "wheelList")
    private List<String> wheels;
}
自定义工夫格式化

在将 Date 类型的属性,转换为 String 类型时,能够通过 dateFormat 来指定工夫格式化:

@AutoMapper(target = Goods.class)
public class GoodsDto {@AutoMapping(target = "takeDownTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date takeDownTime;

}
自定义数字格式化

当数字类型(doublelongBigDecimal)转换为 String 类型时,能够通过 numberFormat 指定 java.text.DecimalFormat 所反对的格局:

@AutoMapper(target = Goods.class)
public class GoodsDto {@AutoMapping(target = "price", numberFormat = "$#.00")
    private int price;

}
自定义 Java 表达式

@AutoMapping 提供了 expression 属性,反对配置一段可执行的 Java 代码,来执行具体的转换逻辑。

例如,当一个 List<String> 的属性,想要转换为用 , 分隔的字符串时,能够通过该配置,来执行转换逻辑:

@AutoMapper(target = UserDto.class)
public class User {@AutoMapping(target = "educations", expression = "java(java.lang.String.join(",", source.getEducationList()))")
    private List<String> educationList;

}

自定义类型转换器

@AutoMapping 注解提供了 uses 属性,引入自定义的类型转换器,来提供给以后类转换时应用。

实例场景:

我的项目中会有字符串用 , 分隔,在一些类中,须要依据逗号拆分为字符串汇合。针对于这种场景,能够有两种形式:首先能够指定字段映射时的表达式,但须要对每种该状况的字段,都增加表达式,简单且容易出错。

第二,就能够自定义一个类型转换器,通过 uses 来应用

public interface StringToListString {default List<String> stringToListString(String str) {return StrUtil.split(str);
    }
}

@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto {

    private String username;
    private int age;
    private boolean young;
    @AutoMapping(target = "educationList")
    private String educations;
    // ......
}

测试:


@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void ueseTest() {UserDto userDto = new UserDto();
        userDto.setEducations("1,2,3");

        final User user = converter.convert(userDto, User.class);
        System.out.println(user.getEducationList());    // [1, 2, 3]

        assert user.getEducationList().size() == 3;
    }
}

更多

更多个性,能够查看官网文档

结束语

Mapstruct Plus 绝对于 Mapstruct 来说,继承了其高性能的特点,同时加强了其便携性和疾速开发的个性,在零碎模型设计较好(属性及类型基本一致)的状况下,开发成本极低,是时候和 BeanUtils 说再见了。

退出移动版