本文转载自微信公众号「阿Q说代码」,作者阿Q 。转载本文请联系阿Q说代码众号。
兴县网站制作公司哪家好,找成都创新互联!从网页设计、网站建设、微信开发、APP开发、成都响应式网站建设公司等网站项目制作,到程序开发,运营维护。成都创新互联从2013年成立到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选成都创新互联。
前几天,远在北京的小伙伴在群里抛出了“MapStruct”的概念。对于只闻其名,未见其人的我来说,决定对其研究一番。本文我们就从 MapStruct 的概念出发,通过具体的代码示例来研究它的使用情况,最后与“市面上”的其它工具来做个对比!
首先我们打开 MapStruct 的官网地址,映入眼帘的就是下边的三步曲:
What is it?
MapStruct 是一个代码生成器,它基于约定优先于配置的方法大大简化了 JavaBean 类型之间映射的实现。生成的映射代码使用普通方法调用,因此速度快、类型安全且易于理解。
Why?
多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。编写这样的映射代码是一项乏味且容易出错的任务。MapStruct 旨在通过尽可能自动化来简化这项工作。
与其他映射框架不同,MapStruct 在编译时生成 bean 映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
How?
MapStruct 是插入 Java 编译器的注释处理器,可以在命令行构建(Maven、Gradle等)中使用,也可以在首选 IDE 中使用。它使用合理的默认值,但在配置或实现特殊行为时,用户可以自定义实现。
官网的解释总是咬文嚼字,晦涩难懂的,看到这你只需要记住 MapStruct 是用来做实体类映射——实体类拷贝 的就可以了。
源码地址:https://github.com/mapstruct/mapstruct
官网推荐的 Demo: https://github.com/mapstruct/mapstruct-examples
我们注意到官网中有涉及到简单样例的实现,我们用2分钟来分析一波:
org.mapstruct mapstruct-jdk8 1.3.0.Final - //注解处理器,根据注解自动生成mapper的实现
org.mapstruct mapstruct-processor 1.2.0.Final
我们在编译时会报 java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"? 错误,经过查阅资料发现 mapstruct-processor 和 Lombok 的版本需要统一一下:mapstruct-processor:1.2.0.Final , Lombok:1.16.14。
- @NoArgsConstructor
- @AllArgsConstructor
- @Data
- public class Car {
- private String make;
- private int numberOfSeats;
- private CarType type;
- }
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class CarDto {
- private String make;
- private int seatCount;
- private String type;
- }
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
- @Mapping(source = "numberOfSeats", target = "seatCount")
- CarDto carToCarDto(Car car);
- }
解析分析:
我们可以将代码进行编译,然后会发现在 target 文件中生成了 CarMapperImpl.class 文件:
从代码中可以看出 MapStruct 为我们自动生成了 set/get 代码,并且对枚举类进行了特殊处理。
- @Test
- public void shouldMapCarToDto() {
- Car car = new Car( "Morris", 5, CarType.SEDAN );
- CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
- System.out.println(carDto);
- }
执行结果:
小结: MapStruct 基于 mapper 接口,在编译期动态生成 set/get 代码的 class 文件 ,在运行时直接调用该 class 文件。
@Mapper
我们翻开上边提到的 Mapper 注释的源码,该注释的解释是:将接口或抽象类标记为映射器,并通过 MapStruct 激活该类型实现的生成。我们找到其中的 componentModel 属性,默认值为 default,它有四种值供我们选择:
上边我们用的就是默认的方法,当然我们也可以用 @Autowired 来引入接口依赖,此处不再举例,有兴趣的小伙伴可以自己试试!
另外我们可以看下 uses 属性:可以通过定义其他类来完成字段转换,接下来我们来个小例子演示一下:
1. 定义一个 CarVo.java 类
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class CarVo {
- private String make;
- private int seatCount;
- private boolean type;
- }
2. 在 mapper 中定义一个 vo 转为 dto 的方法 CarDto carVoToCarDto(CarVo carVo);
当不加 uses 属性时,查看编译后生成的实现类
- public CarDto carVoToCarDto(CarVo carVo) {
- if (carVo == null) {
- return null;
- } else {
- CarDto carDto = new CarDto();
- carDto.setMake(carVo.getMake());
- carDto.setSeatCount(carVo.getSeatCount());
- carDto.setType(String.valueOf(carVo.isType()));
- return carDto;
- }
- }
3.在 mapper 上增加 uses 属性,并指定自定义的处理类,代码如下:
- @Mapper(uses = {BooleanStrFormat.class})
- public interface CarMapper {
- ......
- }
- /**
- * 自定义的转换类
- */
- @Component
- public class BooleanStrFormat {
- public String toStr(boolean type) {
- if(type){
- return "Y";
- }else{
- return "N";
- }
- }
- public boolean toBoolean(String type) {
- if (type.equals("Y")) {
- return true;
- } else {
- return false;
- }
- }
- }
- /**
- * 查看编译后生成的实现类
- */
- public CarDto carVoToCarDto(CarVo carVo) {
- if (carVo == null) {
- return null;
- } else {
- CarDto carDto = new CarDto();
- carDto.setMake(carVo.getMake());
- carDto.setSeatCount(carVo.getSeatCount());
- //调用自定义的类中的方法
- carDto.setType(this.booleanStrFormat.toStr(carVo.isType()));
- return carDto;
- }
- }
4.客户端代码
- @Test
- public void shouldMapCarVoToDto() {
- CarVo carVo = new CarVo( "Morris", 5, false );
- CarDto carDto = CarMapper.INSTANCE.carVoToCarDto( carVo );
- System.out.println(carDto);
- }
执行结果:
@Mapping
@Mapping 可以用来配置一个 bean 属性或枚举常量的映射,默认是将具有相同名称的属性进行映射,当然也可以用 source、expression 或者 constant 属性手动指定,接下来我们来分析下常用的属性值。
我们用 expression 这个属性来实现一下上边用 uses 实现的案例:
- @Mapping(target = "type", expression = "java(new com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()))")
- CarDto carVoToDtoWithExpression(CarVo carVo);
- @Override
- public CarDto carVoToDtoWithExpression(CarVo carVo) {
- if ( carVo == null ) {
- return null;
- }
- CarDto carDto = new CarDto();
- carDto.setMake( carVo.getMake() );
- carDto.setSeatCount( carVo.getSeatCount() );
- carDto.setType( new com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()) );
- return carDto;
- }
- @Test
- public void mapCarVoToDtoWithExpression() {
- CarVo carVo = new CarVo( "Morris", 5, false );
- CarDto carDto = CarMapper.INSTANCE.carVoToDtoWithExpression( carVo );
- System.out.println(carDto);
- }
运行结果:
至于其他的用法大家可以多多探索。
重要提示:枚举映射功能已被弃用,并被 ValueMapping 取代。它将在后续版本中删除。
可以配置多个 @Mapping,例如
- @Mappings({
- @Mapping(source = "id", target = "carId"),
- @Mapping(source = "name", target = "carName"),
- @Mapping(source = "color", target = "carColor")
- })
用于更新已有对象,还是用例子来说明吧:
1. 创建 BMWCar.java 类
- @NoArgsConstructor
- @AllArgsConstructor
- @Data
- public class BMWCar {
- private String make;
- private int numberOfSeats;
- private CarType type;
- private String color;
- private String price;
- }
2. mapper 中创建更新方法,并查看实现类
- // 更新方法
- void updateBwmCar(Car car, @MappingTarget BMWCar bwmCar);
- // 实现类
- public void updateBwmCar(Car car, BMWCar bwmCar) {
- if (car != null) {
- bwmCar.setMake(car.getMake());
- bwmCar.setNumberOfSeats(car.getNumberOfSeats());
- bwmCar.setType(car.getType());
- }
- }
3. 客户端代码
- @Test
- public void updateBwmCar() {
- Car car = new Car( "Morris", 5, CarType.SEDAN );
- BMWCar bwmCar = new BMWCar("BWM", 5, CarType.SPORTS, "RED", "50w");
- System.out.println("更新前 car:"+car.toString());
- System.out.println("更新前 BWMCar:"+bwmCar.toString());
- CarMapper.INSTANCE.updateBwmCar(car, bwmCar);
- System.out.println("更新后 car:"+car.toString());
- System.out.println("更新后 BWMCar:"+bwmCar.toString());
- }
执行结果:
1. 准备实体类 Benz4SMall.java 和 Mall4S.java
- @NoArgsConstructor
- @AllArgsConstructor
- @Data
- public class Mall4S {
- private String address;
- private String mobile;
- }
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class Benz4SMall {
- private String address;
- private String mobile;
- private String make;
- private int numberOfSeats;
- }
2. mapper 创建转换方法并查看生成的实现类
- Benz4SMall mallCarToBenzMall(Car car, Mall4S mall4S);
- /**
- * 实现类
- */
- public Benz4SMall mallCarToBenzMall(Car car, Mall4S mall4S) {
- if (car == null && mall4S == null) {
- return null;
- } else {
- Benz4SMall benz4SMall = new Benz4SMall();
- if (car != null) {
- benz4SMall.setMake(car.getMake());
- benz4SMall.setNumberOfSeats(car.getNumberOfSeats());
- }
- if (mall4S != null) {
- benz4SMall.setAddress(mall4S.getAddress());
- benz4SMall.setMobile(mall4S.getMobile());
- }
- return benz4SMall;
- }
- }
3. 客户端
- @Test
- public void mallCarToBenzMall() {
- Car car = new Car( "Morris", 5, CarType.SEDAN );
- Mall4S mall4S = new Mall4S("北京市", "135XXXX4503");
- Benz4SMall benz4SMall = CarMapper.INSTANCE.mallCarToBenzMall(car, mall4S);
- System.out.println(benz4SMall.toString());
- }
执行结果:
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设 B 复制了 A ,修改 A 的时候,看 B 是否发生变化:如果 B 跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值);如果 B 没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
MapStruct 中是创建新的对象,也就是深拷贝。
MapStruct 与其他 Copy 的对比
我们在平时的项目中经常会使用到拷贝的功能,今天我们就将他们做一下对比,直接抛出 ZhaoYingChao88 大佬的实验结果:
输出结果:手动Copy >Mapstuct>= cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解为: 手工复制 > cglib > 反射 > Dozer。
根据测试结果,我们可以得出在速度方面,MapStruct 是最好的,执行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。
总结:在大数据量级的情况下,MapStruct 和 BeanCopier 都有着较高的性能优势,其中 MapStruct 尤为优秀。如果你仅是在日常处理少量的对象时,选取哪个其实变得并不重要,但数据量大时建议还是使用 MapStruct 或 BeanCopier 的方式,提高接口性能。
网站栏目:还在用BeanUtils拷贝对象?MapStruct才是王者!
本文路径:http://www.shufengxianlan.com/qtweb/news45/75145.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联