平時做項目的時候,經常需要做PO、VO、DTO之間的轉換。簡單的對象轉換,使用BeanUtils基本上是夠了,但是復雜的轉換,如果使用它的話又得寫一堆Getter、Setter方法了。今天給大家推薦一款對象自動映射工具MapStruct,功能真心強大!
?
關于BeanUtils
平時我經常使用Hutool中的BeanUtil類來實現對象轉換,用多了之后就發現有些缺點:
對于這些不足,MapStruct都能解決,不愧為一款功能強大的對象映射工具!
?
MapStruct簡介
MapStruct是一款基于Java注解的對象屬性映射工具,在Github上已經有4.5K+Star。使用的時候我們只要在接口中定義好對象屬性映射規則,它就能自動生成映射實現類,不使用反射,性能優秀,能實現各種復雜映射。
?
IDEA插件支持
作為一款非常流行的對象映射工具,MapStruct還提供了專門的IDEA插件,我們在使用之前可以先安裝好插件。
項目集成
在SpingBoot中集成MapStruct非常簡單,僅續添加如下兩個依賴即可,這里使用的是1.4.2.Final版本。
<dependency><!--MapStruct相關依賴--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${mapstruct.version}</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version><scope>compile</scope></dependency>
</dependencies>
?
基本使用
集成完MapStruct之后,我們來體驗下它的功能吧,看看它有何神奇之處!
基本映射
我們先來個快速入門,體驗一下MapStruct的基本功能,并聊聊它的實現原理。
/***?購物會員*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?Member?{private?Long?id;private?String?username;private?String?password;private?String?nickname;private?Date?birthday;private?String?phone;private?String?icon;private?Integer?gender;
}
/***?購物會員Dto*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?MemberDto?{private?Long?id;private?String?username;private?String?password;private?String?nickname;//與PO類型不同的屬性private?String?birthday;//與PO名稱不同的屬性private?String?phoneNumber;private?String?icon;private?Integer?gender;
}
/***?會員對象映射*?Created?by?macro?on?2021/10/21.*/
@Mapper
public?interface?MemberMapper?{MemberMapper?INSTANCE?=?Mappers.getMapper(MemberMapper.class);@Mapping(source?=?"phone",target?=?"phoneNumber")@Mapping(source?=?"birthday",target?=?"birthday",dateFormat?=?"yyyy-MM-dd")MemberDto?toDto(Member?member);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"基本映射")@GetMapping("/baseMapping")public?CommonResult?baseTest()?{List<Member>?memberList?=?LocalJsonUtil.getListFromJson("json/members.json",?Member.class);MemberDto?memberDto?=?MemberMapper.INSTANCE.toDto(memberList.get(0));return?CommonResult.success(memberDto);}
}
public?class?MemberMapperImpl?implements?MemberMapper?{public?MemberMapperImpl()?{}public?MemberDto?toDto(Member?member)?{if?(member?==?null)?{return?null;}?else?{MemberDto?memberDto?=?new?MemberDto();memberDto.setPhoneNumber(member.getPhone());if?(member.getBirthday()?!=?null)?{memberDto.setBirthday((new?SimpleDateFormat("yyyy-MM-dd")).format(member.getBirthday()));}memberDto.setId(member.getId());memberDto.setUsername(member.getUsername());memberDto.setPassword(member.getPassword());memberDto.setNickname(member.getNickname());memberDto.setIcon(member.getIcon());memberDto.setGender(member.getGender());return?memberDto;}}
}
集合映射
MapStruct也提供了集合映射的功能,可以直接將一個PO列表轉換為一個DTO列表,再也不用一個個對象轉換了!
/***?會員對象映射*?Created?by?macro?on?2021/10/21.*/
@Mapper
public?interface?MemberMapper?{MemberMapper?INSTANCE?=?Mappers.getMapper(MemberMapper.class);@Mapping(source?=?"phone",target?=?"phoneNumber")@Mapping(source?=?"birthday",target?=?"birthday",dateFormat?=?"yyyy-MM-dd")List<MemberDto>?toDtoList(List<Member>?list);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"集合映射")@GetMapping("/collectionMapping")public?CommonResult?collectionMapping()?{List<Member>?memberList?=?LocalJsonUtil.getListFromJson("json/members.json",?Member.class);List<MemberDto>?memberDtoList?=?MemberMapper.INSTANCE.toDtoList(memberList);return?CommonResult.success(memberDtoList);}
}
子對象映射
MapStruct對于對象中包含子對象也需要轉換的情況也是有所支持的。
/***?訂單*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?Order?{private?Long?id;private?String?orderSn;private?Date?createTime;private?String?receiverAddress;private?Member?member;private?List<Product>?productList;
}
/***?訂單Dto*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?OrderDto?{private?Long?id;private?String?orderSn;private?Date?createTime;private?String?receiverAddress;//子對象映射Dtoprivate?MemberDto?memberDto;//子對象數組映射Dtoprivate?List<ProductDto>?productDtoList;
}
/***?訂單對象映射*?Created?by?macro?on?2021/10/21.*/
@Mapper(uses?=?{MemberMapper.class,ProductMapper.class})
public?interface?OrderMapper?{OrderMapper?INSTANCE?=?Mappers.getMapper(OrderMapper.class);@Mapping(source?=?"member",target?=?"memberDto")@Mapping(source?=?"productList",target?=?"productDtoList")OrderDto?toDto(Order?order);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"子對象映射")@GetMapping("/subMapping")public?CommonResult?subMapping()?{List<Order>?orderList?=?getOrderList();OrderDto?orderDto?=?OrderMapper.INSTANCE.toDto(orderList.get(0));return?CommonResult.success(orderDto);}
}
合并映射
MapStruct也支持把多個對象屬性映射到一個對象中去。
/***?會員商品信息組合Dto*?Created?by?macro?on?2021/10/21.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?MemberOrderDto?extends?MemberDto{private?String?orderSn;private?String?receiverAddress;
}
/***?會員對象映射*?Created?by?macro?on?2021/10/21.*/
@Mapper
public?interface?MemberMapper?{MemberMapper?INSTANCE?=?Mappers.getMapper(MemberMapper.class);@Mapping(source?=?"member.phone",target?=?"phoneNumber")@Mapping(source?=?"member.birthday",target?=?"birthday",dateFormat?=?"yyyy-MM-dd")@Mapping(source?=?"member.id",target?=?"id")@Mapping(source?=?"order.orderSn",?target?=?"orderSn")@Mapping(source?=?"order.receiverAddress",?target?=?"receiverAddress")MemberOrderDto?toMemberOrderDto(Member?member,?Order?order);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"組合映射")@GetMapping("/compositeMapping")public?CommonResult?compositeMapping()?{List<Order>?orderList?=?LocalJsonUtil.getListFromJson("json/orders.json",?Order.class);List<Member>?memberList?=?LocalJsonUtil.getListFromJson("json/members.json",?Member.class);Member?member?=?memberList.get(0);Order?order?=?orderList.get(0);MemberOrderDto?memberOrderDto?=?MemberMapper.INSTANCE.toMemberOrderDto(member,order);return?CommonResult.success(memberOrderDto);}
}
進階使用
通過上面的基本使用,大家已經可以玩轉MapStruct了,下面我們再來介紹一些進階的用法。
使用依賴注入
上面我們都是通過Mapper接口中的INSTANCE實例來調用方法的,在Spring中我們也是可以使用依賴注入的。
/***?會員對象映射(依賴注入)*?Created?by?macro?on?2021/10/21.*/
@Mapper(componentModel?=?"spring")
public?interface?MemberSpringMapper?{@Mapping(source?=?"phone",target?=?"phoneNumber")@Mapping(source?=?"birthday",target?=?"birthday",dateFormat?=?"yyyy-MM-dd")MemberDto?toDto(Member?member);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@Autowiredprivate?MemberSpringMapper?memberSpringMapper;@ApiOperation(value?=?"使用依賴注入")@GetMapping("/springMapping")public?CommonResult?springMapping()?{List<Member>?memberList?=?LocalJsonUtil.getListFromJson("json/members.json",?Member.class);MemberDto?memberDto?=?memberSpringMapper.toDto(memberList.get(0));return?CommonResult.success(memberDto);}
}
使用常量、默認值和表達式
使用MapStruct映射屬性時,我們可以設置屬性為常量或者默認值,也可以通過Java中的方法編寫表達式來自動生成屬性。
/***?商品*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?Product?{private?Long?id;private?String?productSn;private?String?name;private?String?subTitle;private?String?brandName;private?BigDecimal?price;private?Integer?count;private?Date?createTime;
}
/***?商品Dto*?Created?by?macro?on?2021/10/12.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?ProductDto?{//使用常量private?Long?id;//使用表達式生成屬性private?String?productSn;private?String?name;private?String?subTitle;private?String?brandName;private?BigDecimal?price;//使用默認值private?Integer?count;private?Date?createTime;
}
/***?商品對象映射*?Created?by?macro?on?2021/10/21.*/
@Mapper(imports?=?{UUID.class})
public?interface?ProductMapper?{ProductMapper?INSTANCE?=?Mappers.getMapper(ProductMapper.class);@Mapping(target?=?"id",constant?=?"-1L")@Mapping(source?=?"count",target?=?"count",defaultValue?=?"1")@Mapping(target?=?"productSn",expression?=?"java(UUID.randomUUID().toString())")ProductDto?toDto(Product?product);
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"使用常量、默認值和表達式")@GetMapping("/defaultMapping")public?CommonResult?defaultMapping()?{List<Product>?productList?=?LocalJsonUtil.getListFromJson("json/products.json",?Product.class);Product?product?=?productList.get(0);product.setId(100L);product.setCount(null);ProductDto?productDto?=?ProductMapper.INSTANCE.toDto(product);return?CommonResult.success(productDto);}
}
在映射前后進行自定義處理
MapStruct也支持在映射前后做一些自定義操作,類似AOP中的切面。
/***?商品對象映射(自定義處理)*?Created?by?macro?on?2021/10/21.*/
@Mapper(imports?=?{UUID.class})
public?abstract?class?ProductRoundMapper?{public?static?ProductRoundMapper?INSTANCE?=?Mappers.getMapper(ProductRoundMapper.class);@Mapping(target?=?"id",constant?=?"-1L")@Mapping(source?=?"count",target?=?"count",defaultValue?=?"1")@Mapping(target?=?"productSn",expression?=?"java(UUID.randomUUID().toString())")public?abstract?ProductDto?toDto(Product?product);@BeforeMappingpublic?void?beforeMapping(Product?product){//映射前當price<0時設置為0if(product.getPrice().compareTo(BigDecimal.ZERO)<0){product.setPrice(BigDecimal.ZERO);}}@AfterMappingpublic?void?afterMapping(@MappingTarget?ProductDto?productDto){//映射后設置當前時間為createTimeproductDto.setCreateTime(new?Date());}
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"在映射前后進行自定義處理")@GetMapping("/customRoundMapping")public?CommonResult?customRoundMapping()?{List<Product>?productList?=?LocalJsonUtil.getListFromJson("json/products.json",?Product.class);Product?product?=?productList.get(0);product.setPrice(new?BigDecimal(-1));ProductDto?productDto?=?ProductRoundMapper.INSTANCE.toDto(product);return?CommonResult.success(productDto);}
}
處理映射異常
代碼運行難免會出現異常,MapStruct也支持處理映射異常。
/***?商品驗證異常類*?Created?by?macro?on?2021/10/22.*/
public?class?ProductValidatorException?extends?Exception{public?ProductValidatorException(String?message)?{super(message);}
}
/***?商品驗證異常處理器*?Created?by?macro?on?2021/10/22.*/
public?class?ProductValidator?{public?BigDecimal?validatePrice(BigDecimal?price)?throws?ProductValidatorException?{if(price.compareTo(BigDecimal.ZERO)<0){throw?new?ProductValidatorException("價格不能小于0!");}return?price;}
}
/***?商品對象映射(處理映射異常)*?Created?by?macro?on?2021/10/21.*/
@Mapper(uses?=?{ProductValidator.class},imports?=?{UUID.class})
public?interface?ProductExceptionMapper?{ProductExceptionMapper?INSTANCE?=?Mappers.getMapper(ProductExceptionMapper.class);@Mapping(target?=?"id",constant?=?"-1L")@Mapping(source?=?"count",target?=?"count",defaultValue?=?"1")@Mapping(target?=?"productSn",expression?=?"java(UUID.randomUUID().toString())")ProductDto?toDto(Product?product)?throws?ProductValidatorException;
}
/***?MapStruct對象轉換測試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對象轉換測試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"處理映射異常")@GetMapping("/exceptionMapping")public?CommonResult?exceptionMapping()?{List<Product>?productList?=?LocalJsonUtil.getListFromJson("json/products.json",?Product.class);Product?product?=?productList.get(0);product.setPrice(new?BigDecimal(-1));ProductDto?productDto?=?null;try?{productDto?=?ProductExceptionMapper.INSTANCE.toDto(product);}?catch?(ProductValidatorException?e)?{e.printStackTrace();}return?CommonResult.success(productDto);}
}
總結
通過上面對MapStruct的使用體驗,我們可以發現MapStruct遠比BeanUtils要強大。當我們想實現比較復雜的對象映射時,通過它可以省去寫Getter、Setter方法的過程。當然上面只是介紹了MapStruct的一些常用功能,它的功能遠不止于此,感興趣的朋友可以查看下官方文檔。
參考資料
官方文檔:https://mapstruct.org/documentation/stable/reference/html
項目源碼地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mapstruct
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。