MapStruct是基于JSR 269的Java注解处理器,因此可以在命令行构建中使用(javac、Ant、Maven等等),可以在IDE内使用。用于生成类型安全的bean映射类的Java注解处理器。属于编译时注解,如果转换bean内容有变化。需要手动clean下才能将变化的内容体现到class文件中。说白了就是通过注解的形式帮我们生成set,get方法。
最大的功能就是为我们转换两个不同的bean,或者List,比如拿java的代码规范来说。客户端入参–>系统的业务层入参—>db数据库的入参;就对这3个层次来说。一个req绝对是不能透传到db层的。为什么呢?就是为了降低耦合。提高业务的单一性,那么某一层参数有改变不会影响其他层。
接口注入和静态方法调用
<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
<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>
@Data
public class PersonDto {
private String name;
private String sex;
private Date birthday;
private int age;
private Double money;
private String password;
private Date createTime;
}
//-----------------------------------
@Data
public class PersonVo {
private String fullName;
private String sex;
private String birthday;
private int age;
private Double money;
private String password;
}
//-----------------------------------
@Mapper
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
@Mapping( source = "fullName",target ="name")
@Mapping( target ="birthday", dateFormat = "yyyy-MM-dd")
PersonDto vo2dto(PersonVo vo);
@Mapping( source = "name",target ="fullName")
@Mapping( target ="birthday", dateFormat = "yyyy-MM-dd")
@Mapping( target ="password",ignore = true)
PersonVo dto2vo(PersonDto dto);
}
//-----------------------------------
ublic static void test1() {
PersonDto dto = new PersonDto();
dto.setAge(10);
dto.setBirthday(Date.from(Instant.now()));
dto.setMoney(2.543D);
dto.setName("john");
dto.setSex("F");
dto.setPassword("password");
dto.setCreateTime(new Date());
System.out.println("dto:::" + dto.toString());
PersonVo vo = PersonMapper.INSTANCE.dto2vo(dto);
System.out.println("vo:::" + vo.toString());
PersonDto dto2 = PersonMapper.INSTANCE.vo2dto(vo);
System.out.println("dto2:::" + dto2.toString());
}
//--------------OUTPUT:
dto:::PersonDto(name=john, sex=F, birthday=Wed Apr 07 11:53:05 CST 2021, age=10, money=2.543, password=password, createTime=Wed Apr 07 11:53:05 CST 2021)
vo:::PersonVo(fullName=john, sex=F, birthday=2021-04-07, age=10, money=2.543, password=null)
dto2:::PersonDto(name=john, sex=F, birthday=Wed Apr 07 00:00:00 CST 2021, age=10, money=2.543, password=null, createTime=null)
处理了以下几种情况:
- 相同属性名称自动隐式映射
- 属性名不同,需要指定@mapping
- 类型不同,需要指定转换格式
- 不需要设置的字段,使用
ignore
。如果字段在目标对象上不匹配的,则会自动忽略。
自动生成代码
package demon.study.mapstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-04-07T11:51:35+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class PersonMapperImpl implements PersonMapper {
@Override
public PersonDto vo2dto(PersonVo vo) {
if ( vo == null ) {
return null;
}
PersonDto personDto = new PersonDto();
personDto.setName( vo.getFullName() );
personDto.setSex( vo.getSex() );
try {
if ( vo.getBirthday() != null ) {
personDto.setBirthday( new SimpleDateFormat( "yyyy-MM-dd" ).parse( vo.getBirthday() ) );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
personDto.setAge( vo.getAge() );
personDto.setMoney( vo.getMoney() );
personDto.setPassword( vo.getPassword() );
return personDto;
}
@Override
public PersonVo dto2vo(PersonDto dto) {
if ( dto == null ) {
return null;
}
PersonVo personVo = new PersonVo();
personVo.setFullName( dto.getName() );
personVo.setSex( dto.getSex() );
if ( dto.getBirthday() != null ) {
personVo.setBirthday( new SimpleDateFormat( "yyyy-MM-dd" ).format( dto.getBirthday() ) );
}
personVo.setAge( dto.getAge() );
personVo.setMoney( dto.getMoney() );
return personVo;
}
}
@Data
public class A1 {
private String a;
private String b;
}
@Data
public class A2 {
private String c;
}
@Data
public class A {
private String a;
private String b;
private String c;
}
@Mapper
public interface AMapper {
AMapper INSTANCE = Mappers.getMapper( AMapper.class );
@Mapping(source = "a1.a", target = "a")
@Mapping(source = "a1.b", target = "b")
@Mapping(source = "a2.c", target = "c")
A two2one(A1 a1,A2 a2 );
}
//-----------------output:
自动生成代码
public class AMapperImpl implements AMapper {
@Override
public A two2one(A1 a1, A2 a2) {
if ( a1 == null && a2 == null ) {
return null;
}
A a = new A();
if ( a1 != null ) {
a.setA( a1.getA() );
a.setB( a1.getB() );
}
if ( a2 != null ) {
a.setC( a2.getC() );
}
return a;
}
}
枚举与Integer型不能互相转换,需要自定义(枚举与String 可能会转换正确,Int向枚举也可能转换正确)。
不兼容的类型需要自定义转换。
可以使用@mapper(uses)
,@mapping(expression)
实现
@mapping(expression)
主要用于多个属性与一个属性的映射
public class Custom {
@Getter
public enum CustomType {
EMPTY(0, "EMPTY"),
A(1, "A"),
B(2, "B");
private Integer code;
private String desc;
CustomType(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public String toString() {
return String.format("{%s,%s}", code, desc);
}
public static CustomType from(Integer code) {
switch (code) {
case 1:
return CustomType.A;
case 2:
return CustomType.B;
default:
return CustomType.EMPTY;
}
}
}
@Data
public static class CustomDto {
private Date birthday;
private CustomType customA;
private CustomType customB;
private CustomType customC;
}
@Data
public static class CustomVo {
private long birthday;
private String customA;
private Integer customB;
private Integer customC;
private int all;
}
@Mapper(uses = {Date2IntHandler.class})
public interface CustomMapper {
CustomMapper INSTANCE = Mappers.getMapper(CustomMapper.class);
@Mapping(target = "all", expression = "java(enum2Int(dto.getCustomB()) + enum2Int(dto.getCustomA()) )")
CustomVo dto2vo(CustomDto dto);
CustomDto vo2dto(CustomVo vo);
default CustomType int2Enum(Integer code) {
return CustomType.from(code);
}
default Integer enum2Int(CustomType type) {
return type.getCode();
}
}
public static class Date2IntHandler {
public static Date int2Date(long i) {
return new Date(i);
}
public static long date2Int(Date date) {
return date.getTime();
}
}
public static void main(String[] args) {
test();
}
public static void test() {
CustomDto dto = new CustomDto();
dto.setBirthday(Date.from(Instant.now().minus(Period.ofDays(1000))));
dto.setCustomA(CustomType.A);
dto.setCustomB(CustomType.B);
dto.setCustomC(CustomType.B);
System.out.println("dto:::" + dto.toString());
CustomVo vo = CustomMapper.INSTANCE.dto2vo(dto);
System.out.println("vo:::" + vo.toString());
CustomDto dto2 = CustomMapper.INSTANCE.vo2dto(vo);
System.out.println("dto2:::" + dto2.toString());
}
}
输出:
dto:::Custom.CustomDto(birthday=Thu Jul 12 14:38:04 CST 2018, customA={1,A}, customB={2,B}, customC={2,B})
vo:::Custom.CustomVo(birthday=1531377484863, customA=A, customB=2, customC=2, all=3)
dto2:::Custom.CustomDto(birthday=Thu Jul 12 14:38:04 CST 2018, customA={1,A}, customB={2,B}, customC={2,B})
自动生成代码
public class Custom$CustomMapperImpl implements CustomMapper {
@Override
public CustomVo dto2vo(CustomDto dto) {
if ( dto == null ) {
return null;
}
CustomVo customVo = new CustomVo();
customVo.setBirthday( Date2IntHandler.date2Int( dto.getBirthday() ) );
if ( dto.getCustomA() != null ) {
customVo.setCustomA( dto.getCustomA().name() );
}
customVo.setCustomB( enum2Int( dto.getCustomB() ) );
customVo.setCustomC( enum2Int( dto.getCustomC() ) );
customVo.setAll( enum2Int(dto.getCustomB()) + enum2Int(dto.getCustomA()) );
return customVo;
}
@Override
public CustomDto vo2dto(CustomVo vo) {
if ( vo == null ) {
return null;
}
CustomDto customDto = new CustomDto();
customDto.setBirthday( Date2IntHandler.int2Date( vo.getBirthday() ) );
if ( vo.getCustomA() != null ) {
customDto.setCustomA( Enum.valueOf( CustomType.class, vo.getCustomA() ) );
}
customDto.setCustomB( int2Enum( vo.getCustomB() ) );
customDto.setCustomC( int2Enum( vo.getCustomC() ) );
return customDto;
}
}
注意:
@mapping的属性,
source
和expression
不能同时定义,因为expression中使用了表达式。Maper中定义的转换函数 ,会被自动应用到相同类型属性的转换上,可能会导致非期望结果。
1、接口注入方式
mapStruct接口类的声明
//使用@Mapper(componentModel = "spring") 整合
@Mapper(componentModel = "spring")
public interface ItemInfoConvert {
}
//------------------------------------------------------
//调用:
//直接接口注入,方法调用即可
@Autowired
private ItemInfoConvert itemInfoConvert;
Person person = itemInfoConvert.deliveryDO2DTO(cart);
2、静态方法调用
mapStruct接口类的声明
public interface ItemInfoConvert {
//静态方法
ItemInfoConvert INSTANCE = Mappers.getMapper(ItemInfoConvert.class);
}
//---------------------------------------------
//调用:
Person person= ItemInfoConvert.INSTANCE.deliveryDO2DTO(cart);
@Data
public class PersonDto {
......
/**
* 输入为null,输出则为null
* 为什么名字为toVo0?其实类是支持重载(overload),但是在使用stream时,区分不了是想用实例方法,还是类方法。
* @param dto
* @return
*/
public static PersonVo toVo0(PersonDto dto) {
return PersonMapper.INSTANCE.dto2vo(dto);
}
public PersonVo toVo() {
return PersonMapper.INSTANCE.dto2vo(this);
}
public static PersonDto from(PersonVo vo) {
if (vo == null) {
return null;
}
return PersonMapper.INSTANCE.vo2dto(vo);
}
}
//------------------------------------
@Data
public class PersonVo {
... ...
public static PersonDto toDto0(PersonVo vo) {
if (vo == null)
{
return null;
}
return PersonMapper.INSTANCE.vo2dto(vo);
}
public PersonDto toDto(){
return PersonMapper.INSTANCE.vo2dto(this);
}
public static PersonVo from(PersonDto dto){
if (dto == null)
{
return null;
}
return PersonMapper.INSTANCE.dto2vo(dto);
}
}
//------------------------------------
public static void test0() {
PersonDto dto1 = new PersonDto();
dto1.setAge(10);
dto1.setBirthday(Date.from(Instant.now()));
dto1.setMoney(2.543D);
dto1.setName("john");
dto1.setSex("F");
dto1.setPassword("password");
dto1.setCreateTime(new Date());
PersonDto dto2 = new PersonDto();
dto2.setAge(20);
dto2.setBirthday(Date.from(Instant.now()));
dto2.setMoney(7.543D);
dto2.setName("tom");
dto2.setSex("M");
dto2.setPassword("password");
dto2.setCreateTime(new Date());
List<PersonDto> list = Arrays.asList(dto1, null, dto2);
//未考虑空引用
List<PersonVo> list2 = list.stream().map(dto -> dto.toVo()).collect(Collectors.toList());
//考虑了空引用
List<PersonVo> list3 = list.stream().map(PersonDto::toVo0).collect(Collectors.toList());
}
//------------------------------------
见官方文档,可以实现:
保证相同的数据结构,即参数的类型相同,同string 或者同Integer,类型不同无法转换
Exception in thread "main" java.lang.NoSuchMethodError: demon.study.mapstruct.PersonVo.setBirthday(Ljava/util/Date;)V
必须要使用@Mapping来解决此类问题。
避开关键字,比如:delete等
如果项目中也同时使用到了 Lombok,一定要注意 Lombok的版本要等于或者高于1.18.10,否则会有编译不通过的情况发生
@Mapper:注解在接口、类上,这样 MapStruct 才会去实现该接口
属性componentModel:该属性用于指定实现类的类型,有几个属性值:
@Mappings:配置多个@Mapping
@Mapping:配置属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
控制拷贝属性时,源字段为null时,是否覆盖目标对象。
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) //进行null 检查,如果为null,则不复制属性值。
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT //赋值 默认值
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE //忽略,保持目标对象属性值
The strategy works in a hierarchical fashion.
@Mapping#nullValueCheckStrategy
will override@BeanMapping#nullValueCheckStrategy
,@BeanMapping#nullValueCheckStrategy
will override@Mapper#nullValueCheckStrategy
and@Mapper#nullValueCheckStrategy
will override@MaperConfig#nullValueCheckStrategy
.
NullValuePropertyMappingStrategy
also applies when the presence checker returnsnot present
.