zoukankan      html  css  js  c++  java
  • 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 注解。

  • 相关阅读:
    01-JAVA语言基础(动手动脑)
    大道至简第一章JAVA伪代码形式读后感
    《美国教授写给被开除中国留学生的信》阅读笔记
    《大道至简》读后感
    使用java2Word生成Word文档打不开报错 存在非法字符xml
    继上篇博客对安卓爬虫以及TextView更新的问题解释
    安卓Jsoup爬虫
    AndroidStdio模拟器打不开报错 Guest isn't online after 7 seconds
    MapReduce
    水晶报表报错:log4net初始值问题
  • 原文地址:https://www.cnblogs.com/strongmore/p/13417208.html
Copyright © 2011-2022 走看看