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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM