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()
使用方法
pom.xml 中引入(注意使用1.3.0.Final,1.4.0.Final会有点问题)
1.3.0.Final org.mapstruct mapstruct-jdk8 ${mapstruct.version} org.mapstruct mapstruct-processor ${mapstruct.version} 有这两个类
@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修饰的类),查看它的源码就可以知道很多的细节
还没有评论,来说两句吧...