首先,先了解一下 Mapstruct: Mapstruct 是一个代码生成器,通过定义类转换的接口,自动实现属性转换的具体逻辑。主要为了简化 Java 类型之间转换的实现
Mapstruct Plus 是 Mapstruct 的增强工具,在 Mapstruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更加便捷、优雅。
和 Mapstruct 一样,本质上都是一个基于 JSR 269 的 Java 注释处理器,因此可以由 Maven、Gradle、Ant 等来构建触发。
Mapstruct Plus 内嵌 Mapstruct,和 Mapstruct 完全兼容,如果之前已经使用 Mapstruct,可以无缝替换依赖。
<properties>
<mapstruct-plus.version>最新版本mapstruct-plus.version>
properties>
<dependencies>
<dependency>
<groupId>io.github.linpeiliegroupId>
<artifactId>mapstruct-plus-spring-boot-starterartifactId>
<version>${mapstruct-plus.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>io.github.linpeiliegroupId>
<artifactId>mapstruct-plus-processorartifactId>
<version>${mapstruct-plus.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
由于其已经内嵌 Mapstruct,为了防止不同版本之间的差异,请不要再引入 Mapstruct 相关依赖
与 Mapstruct 整合 lombok 的方式一致。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
path>
<path>
<groupId>io.github.linpeiliegroupId>
<artifactId>mapstruct-plus-processorartifactId>
<version>${mapstruct-plus.version}version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
path>
<path>
<groupId>io.github.linpeiliegroupId>
<artifactId>mapstruct-plus-processorartifactId>
<version>${mapstruct-plus.version}version>
path>
<path>
<groupId>org.projectlombokgroupId>
<artifactId>lombok-mapstruct-bindingartifactId>
<version>0.2.0version>
path>
annotationProcessorPaths>
configuration>
plugin>
plugins>
build>
下面演示如何使用 MapStruct Plus 来映射两个对象。
假设有两个类 UserDto
和 User
,分别表示数据层对象和业务层对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = UserDto.class)
public class User {
private String username;
private int age;
private boolean young;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
private String username;
private int age;
private boolean young;
}
在 User
或者 UserDto
上面增加注解 —— @AutoMapper
,并设置 targetType
为对方类。
@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = UserDto.class)
public class User {
private String username;
private int age;
}
自动生成的转换实现类
@SpringBootTest
public class QuickStartTest {
private static Converter converter = new Converter();
public static void main(String[] args) {
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();
}
}
要实现两个类之间的转换,只需要在其中一个类上增加注解 @AutoMapper
,配置 target
属性,指定目标类即可。
例如:
Car
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
private String name;
private String type;
}
CarDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
private String name;
private String type;
}
测试
@SpringBootTest
public class SimpleConvertTest {
private Converter converter = new Converter();
@Test
public void test() {
Car car = new Car().setName("宝马").setType("小轿车");
CarDto carDto = this.converter.convert(car, CarDto.class);
System.out.println("carDto = " + carDto); // carDto = CarDto(name=宝马, type=小轿车)
}
}
自动生成的转换实现类
该例子表示,会生成 Car
转换为 CarDto
的接口 CarToCarDtoMapper
及实现类 CarToCarDtoMapperImpl
。在生成的转换代码中,源类型(Car
)的所有可读属性将被复制到目标属性类型(CarDto
)的相应属性中。
当一个属性与它的目标实体对应物具有相同的名称时,将会被隐式映射。
除此之外,Mapstruct Plus 会根据当前的默认规则,生成 CarDto
转换为 Car
的接口 CarDtoToCarMapper
及实现类 CarDtoToCarMapperImpl
。如果不想生成该转换逻辑的话,可以通过注解的 reverseConvertGenerate
属性来配置。
当要转换的类中,存在自定义类时,会自动寻找该类型的转换方法。
例如,分别有两组对象模型:汽车(Car
)和座椅(SeatConfiguration
),其中 Car
依赖于 SeatConfiguration
。
分别对应对象如下:
Car
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
private String name;
private String type;
private SeatConfiguration seatConfiguration;
}
CarDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
private String name;
private String type;
private SeatConfiguration seatConfiguration;
}
SeatConfiguration
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = SeatConfigurationDto.class)
public class SeatConfiguration {
private String name;
private Integer price;
}
SeatConfigurationDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class SeatConfigurationDto {
private String name;
private Integer price;
}
测试对象属性转换
@SpringBootTest
public class ObjectConvertTest {
private Converter converter = new Converter();
@Test
public void test() {
SeatConfiguration seatConfiguration = new SeatConfiguration().setName("真皮座椅").setPrice(5000);
Car car = new Car().setName("宝马").setType("小轿车").setSeatConfiguration(seatConfiguration);
CarDto carDto = this.converter.convert(car, CarDto.class);
System.out.println("carDto = " + carDto);
// carDto = CarDto(name=宝马, type=小轿车, seatConfiguration=SeatConfiguration(name=真皮座椅, price=5000))
}
}
自动生成的转换实现类
在上面的例子中,首先会生成 CarToCarDtoMapper
和 SeatConfigurationToSeatConfigurationDtoMapper
两个转换接口,并且在转换 Car
时,会自动使用 SeatConfigurationToSeatConfigurationDtoMapper
来对其中的座椅属性来进行转换。
当不同类型的属性,想要按照自定义的规则进行转换时,可以有两种办法:
@AutoMapping
中配置的 expression
表达式配置@AutoMapper
的 uses
属性来引入方式一可以参考下面的表达式章节。
这里基于方式二,实现将 String
类型的属性,根据逗号分隔,转换为 List
类型的属性:
首先,定义一个类型转换器 —— StringToListStringConverter
:
StringToListStringConverter
@Component
public class StringToListStringConverter {
public static List<String> stringToListString(String str) {
return StrUtil.split(str);
}
}
User
@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = UserDto.class)
public class User {
private String username;
private int age;
private boolean young;
private List<String> educationList;
}
UserDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@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;
}
新增MapstructPlusConfig配置类,注入Converter,方便后续使用
@Configuration
public class MapstructPlusConfig {
@Bean
public Converter converter() {
return new Converter();
}
}
测试自定义类型转换器(StringToListString)
@SpringBootTest
public class CustomerConverterTest {
@Resource
private Converter converter;
@Test
public void test() {
UserDto userDto = new UserDto().setAge(23).setUsername("张三").setYoung(true).setEducations("1,2,3");
User user = converter.convert(userDto, User.class);
System.out.println(user); // User(username=张三, age=23, young=true, educationList=[1, 2, 3])
}
}
当两个类中属性存在不一致的场景时,例如名称、类型等不一致,可以进行自定义转换,通过在属性上面添加 @AutoMapping
,来配置映射规则。
@AutoMapping
注解中,提供了 target
属性,可以配置当前属性与目标类中 target
属性之间映射。
例如,Book
转换为 BookDto
时,name
属性与 bookName
属性相对应:
Book
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = BookDto.class)
public class Book {
@AutoMapping(target = "bookName")
private String name;
}
BookDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class BookDto {
private String bookName;
}
测试不同属性名称映射
@SpringBootTest
public class AttributeConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
Book book = new Book().setName("三国演义");
BookDto bookDto = this.converter.convert(book, BookDto.class);
System.out.println(bookDto); // BookDto(bookName=三国演义)
}
}
@AutoMapping
注解中还提供 source
方法,该配置默认取当前属性的名称,之所以可以配置,是为了适应一种场景,当前类的某个属性,其内部的属性,转换为目标中的属性字段,则可以通过当前属性来配置。
例如:
Goods
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = GoodsVo.class, reverseConvertGenerate = false)
public class Goods {
@AutoMapping(source = "sku.price", target = "price")
private Sku sku;
}
GoodsDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class GoodsVo {
private Integer price;
}
Sku
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Sku {
private String name;
private Integer price;
}
测试AutoMapping的source属性
@SpringBootTest
public class AttributeConvertTest {
@Resource
private Converter converter;
@Test
public void test2() {
Sku sku = new Sku().setName("手机").setPrice(4999);
Goods goods = new Goods().setSku(sku);
GoodsVo goodsVo = this.converter.convert(goods, GoodsVo.class);
System.out.println(goodsVo); // GoodsVo(price=4999)
}
}
当时间类型(例如:Date
、LocalDateTime
、LocalDate
等等)需要和 String
通过指定时间格式进行转换时,可以通过 @AutoMapping
中的 dateFormat
来配置:
例如:
Order
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = OrderEntity.class)
public class Order {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private Date createTime;
@AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
private String date;
}
OrderEntity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = Order.class)
public class OrderEntity {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private String orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private String createTime;
@AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
private LocalDate orderDate;
}
测试时间格式转换
@SpringBootTest
public class TimeConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
Order order = new Order().setOrderTime(LocalDateTime.now()).setCreateTime(new Date()).setDate("2023-07-23");
OrderEntity orderEntity = this.converter.convert(order, OrderEntity.class);
System.out.println(orderEntity); // OrderEntity(orderTime=2023-07-23 22:21:00, createTime=2023_07_23 22:21:00, orderDate=2023-07-23)
OrderEntity orderEntity1 = new OrderEntity().setOrderTime("2023-07-23 22:00:00").setCreateTime("2023_07_23 22:21:00").setOrderDate(LocalDate.now());
Order order1 = this.converter.convert(orderEntity1, Order.class); // Order(orderTime=2023-07-23T22:00, createTime=Sun Jul 23 22:21:00 CST 2023, date=2023-07-23)
System.out.println(order1);
}
}
当数字类型(例如:int
/Integer
等数字基本类型及包装类、BigDecimal
)和 String
之间的转换需要指定数字格式,可以通过 @AutoMapping
的 numberFormat
来配置。
该格式需要 java.text.DecimalFormat
所支持
例如:
Order
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = OrderEntity.class)
public class Order {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private Date createTime;
@AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
private String date;
@AutoMapping(numberFormat = "$0.00")
private BigDecimal orderPrice;
@AutoMapping(numberFormat = "$0.00")
private Integer goodsNum;
}
OrderEntity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = Order.class)
public class OrderEntity {
@AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
private String orderTime;
@AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
private String createTime;
@AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
private LocalDate orderDate;
@AutoMapping(numberFormat = "$0.00")
private String orderPrice;
@AutoMapping(numberFormat = "$0.00")
private String goodsNum;
}
测试数字格式转换
@SpringBootTest
public class NumberConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
Order order = new Order().setOrderPrice(BigDecimal.valueOf(998.99)).setGoodsNum(10000);
OrderEntity orderEntity = this.converter.convert(order, OrderEntity.class);
System.out.println(orderEntity); // OrderEntity(orderTime=null, createTime=null, orderDate=null, orderPrice=$998.99, goodsNum=$10000.00)s
}
}
当在进行转换时,需要忽略指定属性的转换,可以通过 @AutoMapping
的 ignore
来配置。
例如:
Car
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
private String name;
@AutoMapping(target = "type", ignore = true)
private String type;
private SeatConfiguration seatConfiguration;
}
CarDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
private String name;
private String type;
private SeatConfiguration seatConfiguration;
}
测试忽略指定属性的转换
@SpringBootTest
public class IgnoreConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
SeatConfiguration seatConfiguration = new SeatConfiguration().setName("真皮座椅").setPrice(5000);
Car car = new Car().setName("宝马").setType("小轿车").setSeatConfiguration(seatConfiguration);
CarDto carDto = this.converter.convert(car, CarDto.class);
System.out.println(carDto); // CarDto(name=宝马, type=null, seatConfiguration=SeatConfiguration(name=真皮座椅, price=5000))
}
}
@AutoMapping
中的 defaultValue
可以指定在转换属性时,当属性为 null
时,转换到目标类中的默认值。
例如:
DefaultDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = DefaultVo.class)
public class DefaultDto {
@AutoMapping(defaultValue = "18")
private Integer i;
@AutoMapping(defaultValue = "1.32")
private Double d;
@AutoMapping(defaultValue = "true")
private Boolean b;
}
DefaultVo
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class DefaultVo {
private Integer i;
private Double d;
private Boolean b;
}
测试属性转换时的默认值
@SpringBootTest
public class DefaultValueConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
DefaultDto defaultDto = new DefaultDto();
DefaultVo defaultVo = this.converter.convert(defaultDto, DefaultVo.class);
System.out.println(defaultVo); // DefaultVo(i=18, d=1.32, b=true)
}
}
在执行属性转换时,可以通过指定执行一段 Java 代码来进行转换操作,例如,对源对象中的某个属性进行转换后返回。
需要注意的是,在生成时,会直接将表达式插入到转换逻辑中,并不会验证其语法。
例如,将源对象中的 List
属性,通过 ,
拼接为字符串:
User
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = UserDto.class)
public class User {
private String username;
private int age;
private boolean young;
@AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
private List<String> educationList;
}
UserDto
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@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 ExpressionConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
User user = new User().setUsername("张三").setAge(23).setYoung(true).setEducationList(Arrays.asList("1", "2", "3"));
UserDto userDto = this.converter.convert(user, UserDto.class);
System.out.println(userDto); // UserDto(username=张三, age=23, young=true, educations=1,2,3)
}
}
当有的类型转换逻辑比较复杂,可以通过自定义转换接口来实现,即使用 MapStruct 原生的方式。
当使用这种方式时,默认生成的类型转换中,如果有前面提供的类型转换时,会自动引用。
例如:
Car
@AutoMapper(target = CarDto.class)
@Data
public class Car {
private Tyre tyre;
}
CarDto
@Data
public class CarDto {
private TyreDTO tyre;
}
这里定义 Tyre
和 TyreDTO
之间的转换接口:
TyreMapper
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TyreMapper {
TyreDTO tyreToTyreDTO(Tyre tyre);
Tyre tyreDtoToTyre(TyreDTO tyreDTO);
}
生成的 Car
与 CarDto
转换接口的实现类如下:
CarToCarDtoMapperImpl
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-24T15:38:48+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarToCarDtoMapperImpl implements CarToCarDtoMapper {
@Autowired
private TyreMapper tyreMapper;
@Override
public CarDto convert(Car source) {
if ( source == null ) {
return null;
}
CarDto carDto = new CarDto();
carDto.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );
return carDto;
}
@Override
public CarDto convert(Car source, CarDto target) {
if ( source == null ) {
return target;
}
target.setTyre( tyreMapper.tyreToTyreDTO( source.getTyre() ) );
return target;
}
}
CarDtoToCarMapperImpl
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-24T15:38:49+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {
@Autowired
private TyreMapper tyreMapper;
@Override
public Car convert(CarDto source) {
if ( source == null ) {
return null;
}
Car car = new Car();
car.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );
return car;
}
@Override
public Car convert(CarDto source, Car target) {
if ( source == null ) {
return target;
}
target.setTyre( tyreMapper.tyreDtoToTyre( source.getTyre() ) );
return target;
}
}
前面提到,当在一个类上面添加 @AutoMapper
注解时,默认情况下,除了会生成源类到目标类的转换接口,还会生成目标类到源类的转换接口和实现类,这里需要注意的是,默认情况下生成的该转换接口,并没有任何自定义配置,即使在源类中配置了 @AutoMapping
注解。
这里要实现目标类到源类的自定义转换配置,可以有两种方式:
@AutoMapper
注解。这是最建议的方式,当转换双方都有添加该注解时,便不会生成默认的转换接口,即按照自定义的规则进行生成。框架中提供了 @ReverseAutoMapping
注解,该注解就是为了配置目标类到源类的自定义转换规则。
注意
这里需要注意的是,防止配置冲突,一旦添加 @ReverseAutoMapping
注解,在目标类中,便不能添加任何自定义转换注解
@ReverseAutoMapping
注解表示的含义,是目标类到源类转换时,需要指定的自定义转换规则,其中可以配置的属性,与 @AutoMapping
注解一致。
这里有两个属性需要注意,分别是 source
和 target
。
这里的 source
指的是目标类中的属性,target
指的是源类中的属性。
可能会有人这里有疑问,为什么这里的配置像是反的?如果没有,可以直接跳过。
框架设计的时候,所有的属性转换配置,都是基于要转换的类型,该类转换为目标类,想要应用的效果。这里的 source
也应该是来源类中的属性。
如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 @AutoMapping
注解,原封不动拷贝到当前类,再修改注解名称即可。
例如:
Student 只在源类配置正向和反向转换规则
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = StudentDto.class)
public class Student {
@AutoMapping(source = "name", target = "sname")
@ReverseAutoMapping(source = "sname", target = "name")
private String name;
@AutoMapping(source = "age", target = "sage")
@ReverseAutoMapping(source = "sage", target = "age")
private Integer age;
}
StudentDto 在目标类并未配置转换规则
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class StudentDto {
private String sname;
private Integer sage;
}
测试反向属性映射配置
@SpringBootTest
public class ReverseAutoMappingConvertTest {
@Resource
private Converter converter;
@Test
public void test() {
Student student = new Student().setName("张三").setAge(23);
StudentDto studentDto = this.converter.convert(student, StudentDto.class);
System.out.println(studentDto); // StudentDto(sname=张三, sage=23)
StudentDto studentDto1 = new StudentDto().setSname("李四").setSage(18);
Student student1 = this.converter.convert(studentDto1, Student.class);
System.out.println(student1); // Student(name=李四, age=18)
}
}
也会自动生成目标类至源类的转换规则
当一个类型是不可变类型时,之前默认的规则,生成的 T convert(S source, @MappingTarget T target)
可能会存在问题。
所以,可以使用任意包下的 Immutable
注解,标识一个类为不可变类型, 当为不可变类型时,@MappingTarget
没有意义,上面的方法最终生成如下:
public T convert(S source, @MappingTarget T target) {
return target;
}