如何干掉BeanUtils?
写业务代码时,为了保证service的复用和逻辑关系,我们通常需要保持良好的分层结构,比如controller -> service -> dao,职责分明,复用性强,但是带来的问题就是频繁的对象转换。
有一种方式就是每两个对象的转化都编写对应的converter方法,方法内部直接新建对象并为其赋值。这样做最大问题就是代码量骤增,虽然我们可以放在assembler
中保持service代码干净,但依旧避免不了开发成本高的问题,而且后期DO变化时,项目中所有的converter都需要修改。
还有一种解决方案是BeanUtils的对象复制功能,但它是在运行期通过反射做的对象转换,性能非常差。
有没有一种方法在编译器动态进行对象转化呢?
MapStruct介绍
MapStruct是一个在编译器进行代码增强来帮你进行对象转换的工具。
这里放上GitHub的说明:
MapStruct is a Java annotation processor for the generation of type-safe and performant mappers for Java bean classes.
如何使用
Maven坐标
<properties>
<org.mapstruct.version>1.4.2.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>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
代码实例
我们有两个类Car
和CarDto
,声明如下:
@Data
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
}
@Data
public class CarDto {
private String make;
private int seatCount;
private String type;
}
定义一个类型转化接口:
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}
接下来我们看看如何使用这个接口:
@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");
}
打开编译后的class文件,发现自动帮我们把方法生成好了,所以不会有反射实现方式造成的性能问题。
与lombok的冲突
1. 转化的对象所有属性都是空
MapStruct生成的代码中,属性赋值部分依赖于原类中的set/get方法,所以当MapStruct和Lombok同时使用时,如果MapStruct先于Lombok编织代码,由于此时类中还不存在set/get方法,所以会出现new了一个空对象的现象。
解决方式为使Lombok在MapStruct之前引用或者增加maven编译时的执行顺序。
2. 转化的对象没有对父类属性进行赋值
MapStruct默认使用builder模式,而lombok的build没有父类属性(除非父子类均加上@SuperBuilder
注解,而这样做代码侵入较大)
解决方式为在@Mapper
注解中手动关闭builder方式
@Mapper(builder = @Builder(disableBuilder = true))