java注解处理器之MapStruct
介绍
MapStruct是一个可以生成类型安全的,高性能的且无依赖的 JavaBean 映射代码的注解处理器,可以在编译期生成对应的mapping,既没有BeanUtils等工具使用反射的性能问题,又免去了自己写映射代码的繁琐。
使用
简单转换
maven依赖
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct-jdk8</artifactId>
   <version>1.3.0.Final</version>
</dependency>
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct-processor</artifactId>
   <version>1.3.0.Final</version>
</dependency>
先定义两个entity
@Data
public class Source {
  private String id;
  private Integer num;
  private Integer count;
}
@Data
public class Target {
  private String id;
  private Integer num;
  private Integer count;
}
Source 为转换类,Target 为待转换类,接下来定义转换器
@Mapper
public interface SourceMapper {
  SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  Target source2target(Source source);
}
定义一个 INSTANCE 是为了方便调用,方法名没有限制,mapstruct会帮我们生成一个接口的实现类,
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-08-01T19:56:53+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)"
)
public class SourceMapperImpl implements SourceMapper {
    @Override
    public Target source2target(Source source) {
        if ( source == null ) {
            return null;
        }
        Target target = new Target();
        target.setId( source.getId() );
        target.setNum( source.getNum() );
        target.setCount( source.getCount() );
        return target;
    }
}
调用转换器
public class Client {
  public static void main(String[] args) {
    Source source = new Source();
    source.setId("1");
    source.setNum(2);
    source.setCount(3);
    Target target = SourceMapper.INSTANCE.source2target(source);
    System.out.println(source);
    System.out.println(target);
  }
}
输出结果为
Source(id=1, num=2, count=3)
Target(id=1, num=2, count=3)
属性名不同的转换
如果属性名不同的话,可以通过 Mapping 注解来转换
@Data
public class Source {
  private String sourceId;
  private Integer sourceNum;
  private Integer sourceCount;
}
@Data
public class Target {
  private String targetId;
  private Integer targetNum;
  private Integer targetCount;
}
@Mapper
public interface SourceMapper {
  SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  @Mapping(source = "sourceId", target = "targetId")
  @Mapping(source = "sourceNum", target = "targetNum")
  @Mapping(source = "sourceCount", target = "targetCount")
  Target source2target(Source source);
}
Mapping 注解是一个可重复注解,通过 Mapping 注解指定源属性名和目标属性名就可以了。
public class Client {
  public static void main(String[] args) {
    Source source = new Source();
    source.setSourceId("1");
    source.setSourceNum(2);
    source.setSourceCount(3);
    Target target = SourceMapper.INSTANCE.source2target(source);
    System.out.println(source);
    System.out.println(target);
  }
}
结果符合预期。
自定义转换
有时候,某些类型的转换不能通过 mapstruct 来实现,我们可以定义自己的转换逻辑。
@Data
public class Source {
  private String sourceId;
  private Integer sourceNum;
  private Integer sourceCount;
  private SubSource subSource;
}
@Data
public class SubSource {
  private String deleted;
}
@Data
public class Target {
  private String targetId;
  private Integer targetNum;
  private Integer targetCount;
  private SubTarget subTarget;
}
@Data
public class SubTarget {
  private Boolean deleted;
}
定义 SubSource 转换器
@Mapper
public class SubSourceMapper {
  SubTarget subSource2subTarget(SubSource subSource) {
    if (subSource == null) {
      return null;
    }
    SubTarget subTarget = new SubTarget();
// 特殊的转换逻辑
    subTarget.setDeleted(subSource.getDeleted().equals("T"));
    return subTarget;
  }
}
让 SourceMapper 使用自定义的转换器
@Mapper(uses = SubSourceMapper.class)
public interface SourceMapper {
  SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  @Mapping(source = "sourceCount", target = "targetCount")
  @Mapping(source = "sourceNum", target = "targetNum")
  @Mapping(source = "sourceId", target = "targetId")
  @Mapping(source = "subSource", target = "subTarget")
  Target source2target(Source source);
}
Mapper注解的uses属性表示使用的其他转换器,既可以是我们自定义的,也可以是
mapstruct 生成的。java8之后我们也可以通过默认方法的方式来实现自定义转换。
@Mapper
public interface SourceMapper {
  SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
  @Mapping(source = "sourceCount", target = "targetCount")
  @Mapping(source = "sourceNum", target = "targetNum")
  @Mapping(source = "sourceId", target = "targetId")
  @Mapping(source = "subSource", target = "subTarget")
  Target source2target(Source source);
  default SubTarget subSource2subTarget(SubSource subSource) {
    if (subSource == null) {
      return null;
    }
    SubTarget subTarget = new SubTarget();
    subTarget.setDeleted(subSource.getDeleted().equals("T"));
    return subTarget;
  }
}
多对一转换
将多个对象转换成一个
@Data
public class Person {
  private String firstName;
  private String lastName;
  private int height;
  private String description;
}
@Data
public class Address {
  private String street;
  private int zipCode;
  private int houseNo;
  private String description;
}
@Data
public class DeliveryAddress {
  private String firstName;
  private String lastName;
  private int height;
  private String street;
  private int zipCode;
  private int houseNumber;
  private String description;
}
@Mapper
public interface AddressMapper {
  AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
  @Mapping(source = "person.description", target = "description")
  @Mapping(source = "address.houseNo", target = "houseNumber")
  DeliveryAddress personAndAddress2DeliveryAddress(Person person, Address address);
}
两个输入源都有description,必须指定一个输入源。
装饰器
装饰器可以让我们在转换前后添加一些额外的逻辑,以上一个程序为例,重新设置DeliveryAddress的description。
public abstract class AddressMapperDecorate implements AddressMapper {
  private final AddressMapper delegate;
  protected AddressMapperDecorate(AddressMapper addressMapper) {
    this.delegate = addressMapper;
  }
// 装饰器逻辑 重新设置description
  @Override
  public DeliveryAddress personAndAddress2DeliveryAddress(Person person, Address address) {
    DeliveryAddress deliveryAddress = delegate.personAndAddress2DeliveryAddress(person, address);
    deliveryAddress.setDescription(person.getDescription() + ":" + address.getDescription());
    return deliveryAddress;
  }
}
定义一个装饰器,必须实现转换接口并添加一个接口的构造器,定义为抽象类可以让我们只装饰指定的方法。
使用DecoratedWith注解来表明所使用的装饰器
@Mapper
@DecoratedWith(AddressMapperDecorate.class)
public interface AddressMapper {
  AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
  @Mapping(source = "person.description", target = "description")
  @Mapping(source = "address.houseNo", target = "houseNumber")
  DeliveryAddress personAndAddress2DeliveryAddress(Person person, Address address);
}
前置后置处理器
我们可以在转换方法调用前后做一些操作
@Mapper
public interface AddressMapper {
  AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
  @Mapping(source = "person.description", target = "description")
  @Mapping(source = "address.houseNo", target = "houseNumber")
  DeliveryAddress personAndAddress2DeliveryAddress(Person person,
                                                   Address address,
                                                   @Context Locale locale);
  @BeforeMapping
  default void beforeMapping(Person person,
                             Address address,
                             @MappingTarget DeliveryAddress deliveryAddress,
                             @TargetType Class<DeliveryAddress> deliveryAddressClass,
                             @Context Locale locale) {
    System.out.println("before mapping start...");
    System.out.println(person);
    System.out.println(address);
    System.out.println(deliveryAddress);
    System.out.println(deliveryAddressClass);
    System.out.println(locale);
    System.out.println("before mapping end...");
  }
  @AfterMapping
  default void afterMapping(Person person,
                            Address address,
                            @MappingTarget DeliveryAddress deliveryAddress) {
    deliveryAddress.setDescription(person.getDescription() + "," + address.getDescription());
  }
}
BeforeMapping 注解表示前置处理器,AfterMapping 注解表示后置处理器,MappingTarget 注解表示此参数为target实例,TargetType 注解表示参数为target类型,Context 注解表示参数为上下文参数,对应转换方法中的上下文,其余的参数为source。
依赖注入
我们也可以将转换器定义为spring的bean
@Mapper(componentModel = "spring")
public interface AddressMapper {
  AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);
  @Mapping(source = "person.description", target = "description")
  @Mapping(source = "address.houseNo", target = "houseNumber")
  DeliveryAddress personAndAddress2DeliveryAddress(Person person, Address address);
}
接口实现类上会加上 Component 注解。
 
         
         
         
         
         
        
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号