MapStruct
MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)
1、产生背景
我们知道拷贝bean可以使用apache的BeanUtils和spring 提供的BeanUtils,阿里巴巴规范明确规定不要使用Apache的BeanUtils,不论是Apache或者是Spring,他们都是基于反射的,而反射的会有大量的属性拷贝,就会占据cpu,可以效率不是太高。而 MapStruct 是在编译时就已经生成了相应的bean,极大的减少了系统允许时cpu的使用量
2、原理
首先,我们先重温下java的编译过程:Java源代码–>编译器–>jvm可执行的Java字节码(即虚拟指令)–>jvm–>jvm中解释器–>机器可执行的二进制机器码–>程序。其实java编译器提供了一套完整的api,我们使用接口可以方便地进行动态编译
在执行JavaCompile#compile中就有去执行processAnnotation(注解扫描与处理)这个步骤。这就是mapstruct注解扫描的入口调用方法
跟踪整个调用链路,最后就会发现MappingProcessor的入口。
这就是“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