mapstruct的使用

mapstruct的使用

背景

我们可能都用过spring的BeanUtils将bean1转成bean2,例如

BeancopyProperties(source, target);

这个工具其实在有些公司是被禁止的,我猜是这几个原因

  • 可读性差了,虽然代码简单了
  • 有些错误不能在编译期就暴露出来,有时候甚至是在运行时也暴露不出来
例如bean1转为bean2,如果bean1有List list,而bean2有List list,由于类型相同,都是List,且变量名相同,所以转换的时候会把bean1的list的指针直接赋给bean2的list变量

(注意:泛型仅仅是在编译期进行约束,但是实际运行时是没有所谓的泛型的,所以List在运行时和List是没什么区别的)

如果此时切好有
for(B b : bean2.getList()) {...}
这时就会发生转换异常!!! 因为bean2里的list其实是bean1的list,是A类型而不是B!!!

但是,如果没有上述代码,即使在运行时也是不会暴露出问题的,所以非常之坑!!!

说完了BeanUtils,说下mapstruct,它也是将bean1转成bean2的工具,但是它不是通过反射来进行的所以效率非常高,而且它是通过注解来实现让IDE自动产生转换代码,相当于帮你手敲了一行行的setXxx()

使用方法

  1. pom.xml 中引入(注意使用1.3.0.Final,1.4.0.Final会有点问题)
<mapstruct.version>1.3.0.Finalmapstruct.version>

<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-jdk8artifactId>
<version>${mapstruct.version}version>
dependency>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${mapstruct.version}version>
dependency>
  1. 有这两个类
@Data
public class Source {
    private String name;
}


@Data
public class Target {
    private String name;
}


// 关键的转换类,使用@Mapper注解(org.mapstruct.Mapper)
@Mapper
public interface Converter {
		// 这个INSTANCE并不是必须的,只是为了方便调用者容易使用
    Converter INSTANCE = Mappers.getMapper(Converter.class);
    // 方法名随意,没关系,具有可读语义即可
    Target s2t(Source source);
}


// 测试类
public class TestDrive {
    public static void main(String[] args) {
        Source source = new Source();
        source.setName("name");
        source.setToBeIgnored("toBeIgnored");

        Target target = Converter.INSTANCE.s2t(source);
        System.out.println(target);
    }
}

使用中的细节

常用
Source Target 编译 运行 其他
有字段 name name 正常 name字段被忽略
有name字段 正常 正常 name字段被忽略,Target会保持new Target()时的name值,意思是如果Target中name保留被new时的值
name name2 正常 正常 由于字段名不同,无法将name转换至name2(特殊配置后可以
Integer age Long age 正常 正常 虽然类型不同,但是值能带过去,由于Integer->Long,属于 “小杯转大杯”,不会溢出。Long/Integer/Short/Byte/long/int/short/byte可两两互转
Long age Integer age 正常 正常 虽然类型不同,同样也能把值带过去,但是由于 “大杯转小杯”,只要源值类型超过目标类型的最大值则会溢出。Long/Integer/Short/Byte/long/int/short/byte可两两互转
Float salary Double salary 正常 正常 虽然类型不同,值能带过去,“小杯转大杯” 不溢出。同样Double可以转Float,可能会溢出。Double/Float/double/float可两两互转
Integer field Boolean field 失败 - 编译失败,类型不像Long/Integer/…或者Double/Float/…具有同类型的特性。
User user User user 正常 正常 Source和Target中相同类型且相同变量名,即使是 “非普通字段”,也是可以进行自动转换的(注1)。Source和Target中的user对象内存地址相同
List userList List userList 正常 正常 能成功转换。Source和Target的userList内存地址相同
User user UserDTO user 正常 正常 Source和Target中的类型不一样,但是变量名相同,这也是会自动推断并进行转换的。Source和Target的user的内存地址显然是不同的
List userList List userList 正常 正常 Source和Target中的类型都是List,虽然类型相同,但是mapstruct框架能够识别泛型的不同,会逐个对立面的元素转换成目标的元素。
  • 注1:普通字段是指String/Long/Integer/Short/Byte/Boolean/Double/Float以及非包装类long/int/short/byte/boolean/double/float/char)
补充
  • 如果某个字段不想被转

    @Mapper
    public interface Converter {
        Converter INSTANCE = Mappers.getMapper(Converter.class);
        @Mapping(source = "toBeIgnored", target = "toBeIgnored", ignore = true)
        Target s2t(Source source);
    }
    
    
    @Data
    public class Source {
        private String name;
        private String toBeIgnored;
    }
    
    @Data
    public class Target {
        private String name;
        private String toBeIgnored;
    }
    
  • 如果字段名不一样

    @Mapper
    public interface Converter {
        Converter INSTANCE = Mappers.getMapper(Converter.class);
        @Mapping(source = "name", target = "name2")
        Target s2t(Source source);
    }
    
    @Data
    public class Target {
        private String name;
    }
    
    
    @Data
    public class Source {
        private String name2;
    }
    
  • 如果Source和Target中的Food类的字段不同

    @Mapper
    public interface Converter {
        Converter INSTANCE = Mappers.getMapper(Converter.class);
        @Mapping(source = "food.aFoodName", target = "food.bFoodName")
        Target s2t(Source source);
    }
    
    @Data
    public class Source {
        private AFood food;
    }
    @Data
    public class Target {
        private BFood food;
    }
    @Data
    public class AFood {
        private String aFoodName;
    }
    @Data
    public class BFood {
        private String bFoodName;
    }
    //------------------------------------------
    // 假如source和Target中关于AFood和BFood的变量名也不同,则需要在上述的基础上加上一条新的注解
    @Mapping(source = "afood", target = "bfood")// 新增
    @Mapping(source = "afood.aFoodName", target = "bfood.bFoodName")
    Target s2t(Source source);
    
  • 如果Source和Target中存在List,而List中的元素的字段名不同

    要怎么写@Mapping呢?

    @Mapper
    public interface Converter {
        Converter INSTANCE = Mappers.getMapper(Converter.class);
      
      	@Mapping(source = "aFoodList", target = "bFoodList")
        @Mapping(source = "aFoodList.aFoodName", target = "bFoodList.bFoodName")// 这个写法不行,无论换成 aFoodList.$.aFoodName,aFoodList.*.aFoodName,aFoodList[*].aFoodName,各种写法都不行
        Target s2t(Source source);
    }
    
    @Data
    public class Source {
        private List<AFood> aFoodList;
    }
    @Data
    public class Target {
        private List<BFood> bFoodList;
    }
    @Data
    public class AFood {
        private String aFoodName;
    }
    @Data
    public class BFood {
        private String bFoodName;
    }
    

    解决办法是再写一个转换方法,它自动会利用起来,例如

    @Mapper
    public interface Converter {
        Converter INSTANCE = Mappers.getMapper(Converter.class);
        @Mapping(source = "aFoodList", target = "bFoodList")
        Target s2t(Source source);
      	
      	// 增加这个后回被利用起来,用于s2t的转换
      	@Mapping(source = "aFoodName", target = "bFoodName")
      	BFood a2b(AFood aFood);
    }
    

如何查看生成的代码

我们使用@Mapper(org.mapstruct)时,是会自动产生一些类的,通过查看生成的类,可以知道很多的细节。
只需要command+f9就可以触发编译,以便得到最新生成的类,然后查看target/generated-sources/annotations下面对应的转换类(@Mapper修饰的类),查看它的源码就可以知道很多的细节

你可能感兴趣的:(Java基础/JUC/JVM,mapstruct,beanutils)