介紹
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 注解。