【原創】如何優雅的轉換Bean物件
阿新 • • 發佈:2020-09-07
## 背景
我們的故事要從一個風和日麗的下午開始說起!
這天,外包韓在位置上寫程式碼~外包韓根據如下定義
- **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,希望大家有所收穫!
還想聽到其他關於阿雄的故事麼,請記得關注"孤獨煙!"