吐血整理MapStruct

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的入口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23YQJ7lD-1639925662689)(D:\Typora\note\MapStruct\0d351cff32b969af188036b7e2afe30b.png)]
这就是“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://blog.csdn.net/hxx688/article/details/120786485
参考:https://mapstruct.org/documentation/stable/reference/html/#_advanced_mapping_options

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗罗的1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值