MapStruct版本:1.3.1.Final。
当我们需要进行Java Model之间的拷贝时,或者项目要求Java Model需要严格区分为数据对象(DO)、数据传输对象(DTO)和展示对象(VO)的时候,我们就不得不把一个实体中的属性映射到另一个实体中。最简单的做法就是写一个工具类,进行不断的getter / setter,这样虽然能完成要求但却写了很多冗余代码,维护起来相当恶心。所以这个时候就需要一款能自动映射实体属性的工具了。
Spring自带的BeanUtils工具类算是一款,但是它却不能自定义映射规则;ModelMapper也是一款映射工具框架,虽然它可以自定义映射规则,但写法上却复杂一些。而MapStruct作为一款优秀的Java实体映射工具来说,它也能够自定义映射规则,并且是通过注解的方式来实现的,源和目标看得很清楚明白。不同于BeanUtils和ModelMapper是通过反射在运行期生成代码从而导致性能不高,MapStruct是在编译期生成实现类映射代码,生成的代码就是普通的getter / setter代码,和原生使用的性能相差不大。
1 简单使用
首先需要引入的依赖如下所示:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.1.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</dependency>
除此之外如果使用的IDE是idea的话,还可以下载MapStruct的插件:
该插件可以动态地提示当前没有进行映射的字段,以及其他一些对MapStruct的支持(和Lombok不同,该插件不是必须安装的)。
接着准备两个Java Model如下所示,一个DO,一个VO:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PersonDO implements Serializable {
private static final long serialVersionUID = -3483764417202514211L;
private Long personId;
private String name;
private Integer sex;
private Integer age;
private String address;
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PersonVO implements Serializable {
private static final long serialVersionUID = 7827081422917080855L;
private Long id;
private String name;
private String sex;
private Integer age;
private String address;
}
其中用到了Lombok的注解来简化编程,详见我的另一篇文章《Lombok概述》。在完成了上述准备之后,就可以进行MapStruct的开发使用了。
我们现在是要把PersonDO转成PersonVO,首先需要写一个接口,如下所示:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
PersonVO personDO2VO(PersonDO personDO);
}
之后需要进行打包编译,在对属性进行变动后都要进行打包编译,以此来生成新的实现类。MapStruct并不能及时地反映出属性的变更,比方说接口提供方变更了Model的属性,而调用方只有等到打包编译的时候才能提示出错误,这也许是MapStruct为数不多的缺点了吧,但介于MapStruct是在编译期生成映射代码的机制,这点也无可厚非,只要多加留意即可。
生成的实现类如下所示:
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-02-08T18:46:28+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class PersonMapperImpl implements PersonMapper {
@Override
public PersonVO personDO2VO(PersonDO personDO) {
if ( personDO == null ) {
return null;
}
PersonVO personVO = new PersonVO();
personVO.setName( personDO.getName() );
if ( personDO.getSex() != null ) {
personVO.setSex( String.valueOf( personDO.getSex() ) );
}
personVO.setAge( personDO.getAge() );
personVO.setAddress( personDO.getAddress() );
return personVO;
}
}
由上可以看到,生成的实现类就是通过getter / setter方法来实现的,不损失性能。由此可见我们只需要写一个接口,并定义好映射规则的方法就行了,不需要再写其他的代码。相应的测试代码如下所示:
PersonDO personDO = PersonDO.builder().personId(1L).name("Robert Hou").sex(1).age(24).address("Beijing").build();
PersonVO personVO = PersonMapper.INSTANCE.personDO2VO(personDO);
System.out.println(personVO);
运行结果如下:
PersonVO(id=null, name=Robert Hou, sex=1, age=24, address=Beijing)
2 Spring注入
除了上节的在PersonMapper映射接口中声明INSTANCE的方式来进行调用之外,MapStruct也同时支持Spring的依赖注入机制,如下所示: