mapstruct的使用
背景
我们可能都用过spring的BeanUtils将bean1转成bean2,例如
BeancopyProperties(source, target);
这个工具其实在有些公司是被禁止的,我猜是这几个原因
- 可读性差了,虽然代码简单了
- 有些错误不能在编译期就暴露出来,有时候甚至是在运行时也暴露不出来
例如bean1转为bean2,如果bean1有List<A> list,而bean2有List<B> list,由于类型相同,都是List,且变量名相同,所以转换的时候会把bean1的list的指针直接赋给bean2的list变量
(注意:泛型仅仅是在编译期进行约束,但是实际运行时是没有所谓的泛型的,所以List<A>在运行时和List<B>是没什么区别的)
如果此时切好有
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会有点问题)
<mapstruct.version>1.3.0.Final</mapstruct.version>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
- 有这两个类
@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修饰的类),查看它的源码就可以知道很多的细节
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135248.html