java 注解 map_java注解处理器之MapStruct

介绍

MapStruct是一个可以生成类型安全的,高性能的且无依赖的 JavaBean 映射代码的注解处理器,可以在编译期生成对应的mapping,既没有BeanUtils等工具使用反射的性能问题,又免去了自己写映射代码的繁琐。

使用

简单转换

maven依赖

org.mapstruct

mapstruct-jdk8

1.3.0.Final

org.mapstruct

mapstruct-processor

1.3.0.Final

先定义两个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 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 注解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值