1. 程式人生 > >【原創】如何優雅的轉換Bean物件

【原創】如何優雅的轉換Bean物件

## 背景 我們的故事要從一個風和日麗的下午開始說起! 這天,外包韓在位置上寫程式碼~外包韓根據如下定義 - **PO(persistant object):**持久化物件,可以看成是與資料庫中的表相對映的 java 物件。最簡單的 PO 就是對應資料庫中某個表中的一條記錄。 - **VO(view object):**檢視物件,用於展示層,它的作用是把某個指定頁面(或元件)的所有資料封裝起來。 - **BO(business object):**業務物件,主要作用是把業務邏輯封裝為一個物件。這個物件可以包括一個或多個其它的物件。 - **DTO、DO(省略......)** 將Bean進行逐一分類!例如一個car_tb的表,於是他有了兩個類,一個叫CarPo,裡頭屬性和表字段完全一致。另一個叫CarVo,用於頁面上的Car顯示! 但是外包韓在做CarPo到CarVo轉換的時候,程式碼是這麼寫的,虛擬碼如下: ``` CarPo carPo = this.carDao.selectById(1L); CarVo carVo = new CarVo(); carVo.setId(carPo.getId()); carVo.setName(carPo.getName()); //省略一堆 return carVo; ``` *畫外音:*看到這一串程式碼是不是特別親切,我接手過一堆外包留下的程式碼,就是這麼寫的,一坨屎山!一類幾千行,一半都在set屬性。 恰巧,阿雄打水路過!雞賊的阿雄瞄了一眼外包韓的螢幕,看到外包韓的這一系列程式碼!上去進行一頓教育,覺得不夠優雅!阿雄覺得,應該用`BeanUtils.copyProperties`來簡化書寫,像下面這樣! ``` CarPo carPo = this.carDao.selectById(1L); CarVo carVo = new CarVo(); BeanUtils.copyProperties(carPo, carVo); return carVo; ``` 可是,外包韓盯著這段程式碼,說道:"網上不是說反射效率慢,你這麼寫,沒有效能問題麼?" 阿雄說道:" 如果是用Apache的BeanUtil類,確實有很大的效能問題,像阿里巴巴的程式碼掃描外掛,都禁止用該類,如下所示!" ![](https://img2020.cnblogs.com/blog/725429/202009/725429-20200907003643318-1640711541.jpg) "但是,如果採用的是像Spring的BeanUtils類,要在呼叫次數足夠多的時候,你才能明顯的感受到卡頓。"阿雄補充道。 "哇,阿雄真棒!"外包韓興奮不已! 看著這辦公室基情滿滿的氛圍。一旁正在拖地的清潔工------**掃地煙**,他決定不再沉默。 只見掃地煙扔掉手中的拖把,得瑟的說道"我們不考慮效能。從拓展性角度看看!BeanUtils還是有很多問題的!" - 複製物件時欄位型別不一致,導致賦值不上,你怎麼解決?自己拓展? - 複製物件時欄位名稱不一致,例如CarPo裡叫carName,CarVo裡叫name,導致賦值不上,你怎麼解決?自己拓展? - 如果是集合類的複製,例如List轉換為List,你怎麼處理? (省略一萬字....) "那應該怎麼辦呢?"聽了掃地煙的描述,外包韓疑惑的問道! "很簡單,其實我們在轉換bean的過程中,set這些邏輯是固定的,唯一變化的就是轉換規則。因此,如果我們只需要書寫轉換規則,轉換程式碼由系統根據規則自動生成,就方便很多了!還是用上面的例子,CarPo裡叫carName,CarVo裡叫name,屬性名稱不一致。我們就通過一個註解 ``` @Mapping(source = "carName", target = "name"), ``` 指定對應轉換規則。系統識別到這個註解,就會生成程式碼 ``` carVo.setName(carPo.getCarName()) ``` 如果能以這樣的方式,set程式碼由系統自動生成,那麼在bean轉換邏輯方面,靈活性將大大加強,而且這種方式不存在效能問題!"掃地煙補充道! "那這些set邏輯,由什麼工具來生成呢?"外包韓和阿雄一起問道! "工具的名字叫**MapStruct**!" ok,上面的故事到了這裡,就結束了!不需要問結局,結局只有一個,外包韓和阿雄幸福美滿的...(省略10000字)... 那麼我們開始具體來說一說**MapStruct**! ## MapStruct的教程 這裡從用法、原理、優勢三個角度來介紹一下這個外掛,至於詳細教程,還是看官方文件吧。 ### 用法 引入pom檔案如下 ``` org.mapstruct
mapstruct-jdk8 1.2.0.Final
org.mapstruct mapstruct-processor 1.2.0.Final ``` 在準備兩個實體類,為了方便演示,用了`lombok`外掛。 準備兩個實體類,一個是CarPo ``` @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CarPo { private Integer id; private String brand; } ``` 還有一個是CarVo ``` @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CarVo { private Integer id; private String brand; } ``` 再來一個轉換介面 ``` @Mapper public interface CarCovertBasic { CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class); CarVo toConvertVo(CarPo source); } ``` 測試程式碼如下: ``` //實際中從資料庫取 CarPo carPo = CarPo.builder().id(1) .brand("BMW") .build(); CarVo carVo = CarCovertBasic.INSTANCE.toConvertVo(carPo); System.out.println(carVo); ``` 輸出如下 ``` CarVo(id=1, brand=BMW) ``` 可以看到,carPo的屬性值複製給了carVo。當然,在這種情況下,功能和`BeanUtils`是差不多的,體現不出優勢!嗯,我們放在後面說,我們先來說說原理! ### 原理 其實原理就是MapStruct外掛會識別我們的介面,生成一個實現類,在實現類中,為我們實現了set邏輯! 例如,上面的例子中,給CarCovertBasic介面,實現了一個實現類CarCovertBasicImpl,我們可以用反編譯工具看到原始碼如下圖所示 ![](https://img2020.cnblogs.com/blog/725429/202009/725429-20200907003807946-500246644.png) 下面,我們來說說優勢 ### 優勢 *(1)兩個型別屬性不一致* 此時CarPo的一個屬性為carName,而CarVo對應的屬性為name! 我們在介面上增加對應關係即可,如下所示 ``` @Mapper public interface CarCovertBasic { CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class); @Mapping(source = "carName", target = "name") CarVo toConvertVo(CarPo source); } ``` 測試程式碼如下 ``` CarPo carPo = CarPo.builder().id(1) .brand("BMW") .carName("寶馬") .build(); CarVo carVo = CarCovertBasic.INSTANCE.toConvertVo(carPo); System.out.println(carVo); ``` 輸出如下 ``` CarVo(id=1, brand=BMW, name=寶馬) ``` 可以看到carVo已經能識別到carPo中的carName屬性,並賦值成功。反編譯的圖如下 ![](https://img2020.cnblogs.com/blog/725429/202009/725429-20200907003922487-1102936967.png) `畫外音:`如果有多個對映關係可以用@Mappings註解,巢狀多個@Mapping註解實現,後文說明! *(2)集合型別轉換* 如果我們要從List轉換為List怎麼辦呢? 簡單,接口裡加一個方法就行 ``` @Mapper public interface CarCovertBasic { CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class); @Mapping(source = "carName", target = "name") CarVo toConvertVo(CarPo source); List toConvertVos(List source); } ``` 如程式碼所示,我們增加了一個toConvertVos方法即可,mapStruct生成程式碼的時候,會幫我們去迴圈呼叫toConvertVo方法,給大家看一下反編譯的程式碼,就一目瞭然 ![](https://img2020.cnblogs.com/blog/725429/202009/725429-20200907003930465-1684530368.png) *(3)型別不一致* 在CarPo加一個屬性為Date型別的createTime,而在CarVo加一個屬性為String型別的createTime,那麼程式碼如下 ``` @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CarPo { private Integer id; private String brand; private String carName; private Date createTime; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CarVo { private Integer id; private String brand; private String name; private String createTime; } ``` 介面就可以這麼寫 ``` @Mapper public interface CarCovertBasic { CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class); @Mappings({ @Mapping(source = "carName", target = "name"), @Mapping(target = "createTime", expression = "java(com.guduyan.util.DateUtil.dateToStr(source.getCreateTime()))") }) CarVo toConvertVo(CarPo source); List toConvertVos(List source); } ``` 這樣在程式碼中,就能解決型別不一致的問題!在生成set方法的時候,自動呼叫DateUtil類進行轉換,由於比較簡單,我就不貼反編譯的圖了! *(4)多對一* 在實際業務情況中,我們有時候會遇到將兩個Bean對映為一個Bean的情況,假設我們此時還有一個類為AtrributePo,我們要將CarPo和AttributePo同時對映為CarBo,我們可以這麼寫 ``` @Data @Builder @NoArgsConstructor @AllArgsConstructor public class AttributePo { private double price; private String color; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CarBo { private Integer id; private String brand; private String carName; private Date createTime; private double price; private String color; } ``` 介面改變如下 ``` @Mapper public interface CarCovertBasic { CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class); @Mappings({ @Mapping(source = "carName", target = "name"), @Mapping(target = "createTime", expression = "java(com.guduyan.util.DateUtil.dateToStr(source.getCreateTime()))") }) CarVo toConvertVo(CarPo source); List toConvertVos(List source); CarBo toConvertBo(CarPo source1, AttributePo source2); } ``` 直接增加介面即可,外掛在生成程式碼的時候,會幫我們自動組裝,看看下面的反編譯程式碼就一目瞭然。 ![](https://img2020.cnblogs.com/blog/725429/202009/725429-20200907004008133-725387608.png) *(5)其他* 關於MapStruct還有其他很多的高階功能,我就不一一介紹了。大家可以參考下面的文件,在用到的時候自行翻閱即可! 文件地址:https://mapstruct.org/documentation/reference-guide/ ## 總結 本文介紹了,在專案裡如何優雅的轉換Bean,希望大家有所收穫! 還想聽到其他關於阿雄的故事麼,請記得關注"孤獨煙!"