平時(shí)做項(xiàng)目的時(shí)候,經(jīng)常需要做PO、VO、DTO之間的轉(zhuǎn)換。簡(jiǎn)單的對(duì)象轉(zhuǎn)換,使用BeanUtils基本上是夠了,但是復(fù)雜的轉(zhuǎn)換,如果使用它的話又得寫(xiě)一堆Getter、Setter方法了。今天給大家推薦一款對(duì)象自動(dòng)映射工具M(jìn)apStruct,功能真心強(qiáng)大!
?
關(guān)于BeanUtils
平時(shí)我經(jīng)常使用Hutool中的BeanUtil類(lèi)來(lái)實(shí)現(xiàn)對(duì)象轉(zhuǎn)換,用多了之后就發(fā)現(xiàn)有些缺點(diǎn):
對(duì)象屬性映射使用反射來(lái)實(shí)現(xiàn),性能比較低;
對(duì)于不同名稱或不同類(lèi)型的屬性無(wú)法轉(zhuǎn)換,還得單獨(dú)寫(xiě)Getter、Setter方法;
對(duì)于嵌套的子對(duì)象也需要轉(zhuǎn)換的情況,也得自行處理;
集合對(duì)象轉(zhuǎn)換時(shí),得使用循環(huán),一個(gè)個(gè)拷貝。
對(duì)于這些不足,MapStruct都能解決,不愧為一款功能強(qiáng)大的對(duì)象映射工具!
?
MapStruct簡(jiǎn)介
MapStruct是一款基于Java注解的對(duì)象屬性映射工具,在Github上已經(jīng)有4.5K+Star。使用的時(shí)候我們只要在接口中定義好對(duì)象屬性映射規(guī)則,它就能自動(dòng)生成映射實(shí)現(xiàn)類(lèi),不使用反射,性能優(yōu)秀,能實(shí)現(xiàn)各種復(fù)雜映射。
?
IDEA插件支持
作為一款非常流行的對(duì)象映射工具,MapStruct還提供了專(zhuān)門(mén)的IDEA插件,我們?cè)谑褂弥翱梢韵劝惭b好插件。
項(xiàng)目集成
在SpingBoot中集成MapStruct非常簡(jiǎn)單,僅續(xù)添加如下兩個(gè)依賴即可,這里使用的是1.4.2.Final版本。
<dependency><!--MapStruct相關(guān)依賴--><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之后,我們來(lái)體驗(yàn)下它的功能吧,看看它有何神奇之處!
基本映射
我們先來(lái)個(gè)快速入門(mén),體驗(yàn)一下MapStruct的基本功能,并聊聊它的實(shí)現(xiàn)原理。
/***?購(gòu)物會(huì)員*?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;
}
/***?購(gòu)物會(huì)員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類(lèi)型不同的屬性private?String?birthday;//與PO名稱不同的屬性private?String?phoneNumber;private?String?icon;private?Integer?gender;
}
/***?會(huì)員對(duì)象映射*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@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也提供了集合映射的功能,可以直接將一個(gè)PO列表轉(zhuǎn)換為一個(gè)DTO列表,再也不用一個(gè)個(gè)對(duì)象轉(zhuǎn)換了!
/***?會(huì)員對(duì)象映射*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@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);}
}
子對(duì)象映射
MapStruct對(duì)于對(duì)象中包含子對(duì)象也需要轉(zhuǎn)換的情況也是有所支持的。
/***?訂單*?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;//子對(duì)象映射Dtoprivate?MemberDto?memberDto;//子對(duì)象數(shù)組映射Dtoprivate?List<ProductDto>?productDtoList;
}
/***?訂單對(duì)象映射*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"子對(duì)象映射")@GetMapping("/subMapping")public?CommonResult?subMapping()?{List<Order>?orderList?=?getOrderList();OrderDto?orderDto?=?OrderMapper.INSTANCE.toDto(orderList.get(0));return?CommonResult.success(orderDto);}
}
合并映射
MapStruct也支持把多個(gè)對(duì)象屬性映射到一個(gè)對(duì)象中去。
/***?會(huì)員商品信息組合Dto*?Created?by?macro?on?2021/10/21.*/
@Data
@EqualsAndHashCode(callSuper?=?false)
public?class?MemberOrderDto?extends?MemberDto{private?String?orderSn;private?String?receiverAddress;
}
/***?會(huì)員對(duì)象映射*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@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);}
}
進(jìn)階使用
通過(guò)上面的基本使用,大家已經(jīng)可以玩轉(zhuǎn)MapStruct了,下面我們?cè)賮?lái)介紹一些進(jìn)階的用法。
使用依賴注入
上面我們都是通過(guò)Mapper接口中的INSTANCE實(shí)例來(lái)調(diào)用方法的,在Spring中我們也是可以使用依賴注入的。
/***?會(huì)員對(duì)象映射(依賴注入)*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@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);}
}
使用常量、默認(rèn)值和表達(dá)式
使用MapStruct映射屬性時(shí),我們可以設(shè)置屬性為常量或者默認(rèn)值,也可以通過(guò)Java中的方法編寫(xiě)表達(dá)式來(lái)自動(dòng)生成屬性。
/***?商品*?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;//使用表達(dá)式生成屬性private?String?productSn;private?String?name;private?String?subTitle;private?String?brandName;private?BigDecimal?price;//使用默認(rèn)值private?Integer?count;private?Date?createTime;
}
/***?商品對(duì)象映射*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"使用常量、默認(rèn)值和表達(dá)式")@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);}
}
在映射前后進(jìn)行自定義處理
MapStruct也支持在映射前后做一些自定義操作,類(lèi)似AOP中的切面。
/***?商品對(duì)象映射(自定義處理)*?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){//映射前當(dāng)price<0時(shí)設(shè)置為0if(product.getPrice().compareTo(BigDecimal.ZERO)<0){product.setPrice(BigDecimal.ZERO);}}@AfterMappingpublic?void?afterMapping(@MappingTarget?ProductDto?productDto){//映射后設(shè)置當(dāng)前時(shí)間為createTimeproductDto.setCreateTime(new?Date());}
}
/***?MapStruct對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@RequestMapping("/mapStruct")
public?class?MapStructController?{@ApiOperation(value?=?"在映射前后進(jìn)行自定義處理")@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);}
}
處理映射異常
代碼運(yùn)行難免會(huì)出現(xiàn)異常,MapStruct也支持處理映射異常。
/***?商品驗(yàn)證異常類(lèi)*?Created?by?macro?on?2021/10/22.*/
public?class?ProductValidatorException?extends?Exception{public?ProductValidatorException(String?message)?{super(message);}
}
/***?商品驗(yàn)證異常處理器*?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("價(jià)格不能小于0!");}return?price;}
}
/***?商品對(duì)象映射(處理映射異常)*?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對(duì)象轉(zhuǎn)換測(cè)試Controller*?Created?by?macro?on?2021/10/21.*/
@RestController
@Api(tags?=?"MapStructController",?description?=?"MapStruct對(duì)象轉(zhuǎn)換測(cè)試")
@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);}
}
總結(jié)
通過(guò)上面對(duì)MapStruct的使用體驗(yàn),我們可以發(fā)現(xiàn)MapStruct遠(yuǎn)比BeanUtils要強(qiáng)大。當(dāng)我們想實(shí)現(xiàn)比較復(fù)雜的對(duì)象映射時(shí),通過(guò)它可以省去寫(xiě)Getter、Setter方法的過(guò)程。當(dāng)然上面只是介紹了MapStruct的一些常用功能,它的功能遠(yuǎn)不止于此,感興趣的朋友可以查看下官方文檔。
參考資料
官方文檔:https://mapstruct.org/documentation/stable/reference/html
項(xiàng)目源碼地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-mapstruct
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的干掉 BeanUtils!试试这款 Bean 自动映射工具,真心强大!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。