工作中比较常见的就是各种对象的转化,比方说:从数据库的DO层转换至应用内部的Entity层,从应用内部的Entity层转化至VO/DTO数据通道层,这种转换都是必要的,不仅更好的表述了职责单一性,也可以很好的将依赖隔离开,在接触了各种各样的项目,我还是能发现很多人都在自己去写GetSet方法,又或者是用BeanUtils拷贝对象,这个我现在回过头来看,发觉不是一个比较好的习惯,所以本次想着重介绍下 MapStruct MapStruct是一个代码生成器,它基于基于配置方法的约定,极大地简化了Java bean类型之间映射的实现。生成的映射代码使用普通的方法调用,因此是快速、类型安全和易于理解。
MapStruct使用方式
引入依赖
-- Gradle
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
}
-- Maven
<properties>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.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> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
//constructor, getters, setters etc.
}
public class CarDto {
private String make;
private int seatCount;
private String type;
//constructor, getters, setters etc.
}
创建一个对象转换器
@Mapper 1
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); 3
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car); 2
}
使用的时候只需要使用如下代码
@Test
public void shouldMapCarToDto() {
//given
Car car = new Car( "Morris", 5, CarType.SEDAN );
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
//then
assertThat( carDto ).isNotNull();
assertThat( carDto.getMake() ).isEqualTo( "Morris" );
assertThat( carDto.getSeatCount() ).isEqualTo( 5 );
assertThat( carDto.getType() ).isEqualTo( "SEDAN" );
}
如果使用此种转换方式,不仅逼格提升了,更重要的是代码更加简洁明了
MapStruct原理
我从官方文档没有看到有关实现原理的描述,个人根据使用后的感受猜测了下,上边的例子中有看到一个类型转换器,这个是一个接口,那么它必然是要有一个实现类的,这样就可以知道Mapstruct一定会动态的给我们创建了一个拷贝对象的实现方法,至于如何将某字段赋值给到谁,这个就交给了注解逻辑来控制。
实际我们可以根据一系列操作javac的API对已有代码进行二次加工处理,这个处理方式就是JSR-269协议(插件式注解处理器),下图就是实现规范
我们回过头来想下,MapStruct帮我们做了啥,只是将get/set方法省掉了,程序自动帮助我们实现创建一个返回对象,然后将原对象的属性值设置进去,实际这个动作就是对已有代码的二次填充处理~
MapStruct坑点有哪些
lombok与mapstruct共同使用产生的问题记录
1.在使用注解@AfterMapping未生效,原因是待转换对象使用了@Builder注解
-- 去除@Builder注解
-- (推荐)转换类上边增加@Mapper(builder = @Builder(disableBuilder = true))
-- 将待转换对象改成Builder建造者模式类型