MapStruct

MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)

1、产生背景

我们知道拷贝bean可以使用apache的BeanUtils和spring 提供的BeanUtils,阿里巴巴规范明确规定不要使用Apache的BeanUtils,不论是Apache或者是Spring,他们都是基于反射的,而反射的会有大量的属性拷贝,就会占据cpu,可以效率不是太高。而 MapStruct 是在编译时就已经生成了相应的bean,极大的减少了系统允许时cpu的使用量

吐血整理MapStruct_java

2、原理

首先,我们先重温下java的编译过程:Java源代码–>编译器–>jvm可执行的Java字节码(即虚拟指令)–>jvm–>jvm中解释器–>机器可执行的二进制机器码–>程序。其实java编译器提供了一套完整的api,我们使用接口可以方便地进行动态编译

在执行JavaCompile#compile中就有去执行processAnnotation(注解扫描与处理)这个步骤。这就是mapstruct注解扫描的入口调用方法

吐血整理MapStruct_spring_02

跟踪整个调用链路,最后就会发现MappingProcessor的入口。

吐血整理MapStruct_spring_03

这就是“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用

3、mapstruct的默认映射规则

1、同类型且同名,会自动映射

2、自动类型


  • 8种基本数据类型和它的包装类
  • 8种基本数据类型(包装类)和string
  • 日期类型和string

3、自定义

4、常用注解:

@Mapper
@mapping
@mappings
@BeforeMapping/@AfterMapping/@MappingTarget
@beanMapping
@InheritConfiguration/@MappingTarget
@InheritInverseConfiguration

1、@Mapper

使用位置:抽象类或者接口


接口加了mapper 注解,会在编译时给这个接口创建一个实现类
抽象类加了mapper 注解,会在编译时给这个抽象类创建一个子类


常用参数:

Class<?>[] uses() default { };//导入其他的Mapper
Class<?>[] imports() default { };//导入指定的类
String componentModel() default "default";//default,spring,cdi,jsr330

常见的使用方式

@Mapper(componentModel = "spring")//componentModel表示注入的环境,现在是spring,会发现它的实现类加了component注解
public interface PhoneMapping{

}

2、@mapping

使用位置:方法上或者注解

常用参数

String source() default "";//数据源
String target();//目标对象
String numberFormat() default "";//格式化的数字格式(#.00),使用的是DecimalFormat
String dateFormat() default "";//格式化的日期格式(yyyy-MM-dd)
boolean ignore() default false;//是否忽略映射,不进行映射
String[] qualifiedByName() default { };//根据名字找到对于的处理方法名
String expression() default "";//需要使用java()包裹,里面就是Java代码

表示映射的规则,target是必须要有的

3、@mappings

使用位置:方法上或者注解

常用参数

Mapping[] value();

java8之前

@Mappings({
@Mapping(source = "address", target = "addrs", qualifiedByName = {"strToList"}),
@Mapping(source = "price1",target = "price",numberFormat = "#.00")
})
Telephone source2Target(Phone phone);

java8及其之后

@Mapping(source = "address", target = "addrs", qualifiedByName = {"strToList"})
@Mapping(source = "price1",target = "price",numberFormat = "#.00")
Telephone source2Target(Phone phone);

4、@BeforeMapping、@AfterMapping

使用位置:方法上

无参数

@BeforeMapping
default void computePrice(T source,@MappingTarget K target){
//还没有进行映射,target没有值
}

@AfterMapping
default void computePrice(T source,@MappingTarget K target){
//已经映射完成,target有值
}

5、@BeanMapping

使用位置:方法上

常用参数

boolean ignoreByDefault() default false; //是否忽略所有自动映射规则

6、@InheritConfiguration

使用位置:方法上,和@MappingTarget搭配使用

常用参数

String name() default "";

常见的使用方式,

Mappings({
@Mapping(target="make", source="brand"),
@Mapping(target="seatCount", source="numberOfSeats")
})
CarDto carToCarDto(Car car);

@InheritConfiguration //把@MappingTarget修饰对象上的映射规则继承下来
void updateCarDto(Car car, @MappingTarget CarDto carDto);

7、@InheritInverseConfiguration

使用位置:方法上,和@MappingTarget搭配使用

常用参数

String name() default "";

反方向继承配置,也就是说可以互相

@Mapper
public interface CarMapper {
@Mapping( target = "seatCount", source = "numberOfSeats")
@Mapping( target = "enginePower", source = "engineClass", ignore=true)
CarDto carToDto(Car car);

@InheritInverseConfiguration
@Mapping(target = "numberOfSeats", ignore = true)
Car carDtoToCar(CarDto carDto);
}

5、实战

maven依赖

<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugins>

这是我们要换的source(getter/setter省略)

public class Phone {
private String address;
private String pwd;
private String name;
private double price1;
private double oldPrice;
private double discount;
private Date createTime;
private List<Users> users;
private String prefix;
private String suffix;
private Map<Integer, Object> dateMap;
.......
}

这是我们的目标对象target

public class Telephone {

private List<Address> addrs;
private String pwd;
private String name;
private String price;
private String createTime;
private String fullName;
private String fullNames;
private String statics;
private String users;
Map<String,Object> dateMap;
private double newPrice;
.......

}

这是其他的

public class Address {
private String city;
private String province;
private String street;
......
}

配置映射规则

@Mapper(componentModel = "spring",imports = {NameAddAddress.class})
public interface PhoneMapping{

@Mapping(source = "address", target = "addrs", qualifiedByName = {"strToList"})
@Mapping(source = "price1",target = "price",numberFormat = "#.00")
@Mapping( target = "pwd", ignore = true)
@Mapping( source = "createTime",target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping( target = "users", qualifiedByName = {"list2Str"})
@Mapping(source = "oldPrice",target = "newPrice")
@Mapping(target = "fullName",expression = "java(new com.luo.xianyu_api.NamesUtils().getFullName(phone.getPrefix(),phone.getSuffix()) )")
@Mapping(target = "fullNames",expression = "java(new NameAddAddress().getNameAddress(phone.getName(),phone.getAddress()) )")
@Mapping(target = "statics",expression = "java(NameAddAddress.getNameAddr(phone.getName(),phone.getAddress()) )")
@Mapping(target = "dateMap",qualifiedByName = "longDateMapToStringStringMap")
Telephone source2Target(Phone phone);

@AfterMapping
default void computePrice(Phone phone,@MappingTarget Telephone telephone){
double discount = phone.getDiscount();
double price1 = phone.getPrice1();
telephone.setNewPrice(discount*price1);
}

@Named("strToList")
default List<Address> strToList(String address){
return JSON.parseArray(address,Address.class);
}

@Named("list2Str")
default <T> String list2Str(T t ){
return JSON.toJSONString(t);
}

@MapMapping(valueQualifiedByName="date2str")
@Named("longDateMapToStringStringMap")
Map<String, Object> longDateMapToStringStringMap(Map<Integer, Object> source);

@Named("date2str")
default Object date2str(Object o){
if (o instanceof Date){
return new SimpleDateFormat("yyyy.MM.dd").format(o);
}
return o;
}
}

客户端

@Test
void mapstruct(){
Phone phone = new Phone();
String addr = "[{\"city\":\"北京\",\"province\":\"北京\",\"street\":\"四合院\"},{\"city\":\"成都\",\"province\":\"四川\",\"street\":\"中和街道\"}]";
phone.setAddress(addr);
phone.setPwd("123456");
phone.setName("摩托罗拉");
phone.setPrice1(8888.2345d);
phone.setOldPrice(1000.0d);
phone.setCreateTime(new Date());
phone.setDiscount(0.8d);

HashMap<Integer, Object> map = new HashMap<>();
map.put(12,new Date());
map.put(13,"你好");

phone.setDateMap(map);

phone.setSuffix("小罗");
phone.setPrefix("罗");
Users users = new Users();
users.setAge(23);
users.setName("小红");
users.setAddresses(Lists.newArrayList(new Address("宜宾","四川","万顺街")));

phone.setUsers(Lists.newArrayList(users));

Telephone telephone = phoneMapping.source2Target(phone);
System.out.println(telephone);
}

输出

Telephone[statics=摩托罗拉==============, fullNames=摩托罗拉==>[{"city":"北京","province":"北京","street":"四合院"},{"city":"成都","province":"四川","street":"中和街道"}], fullName=罗小罗, newPrice=7110.587600000001, addrs=[{"city":"北京","province":"北京","street":"四合院"},{"city":"成都","province":"四川","street":"中和街道"}], dateMap={"12":"2021.12.19","13":"你好"}, pwd='null', name='摩托罗拉', price='8888.23', createTime='2021-12-19 22:47:10', users='[{"addresses":[{"city":"宜宾","province":"四川","street":"万顺街"}],"age":23,"name":"小红"}]']

​​

参考:​​https://mapstruct.org/documentation/stable/reference/html/#_advanced_mapping_options​​​