轉(zhuǎn)換Bean對象?BeanUtils?不是吧,阿 Sir
本文公眾號來源:孤獨煙 作者:孤獨煙 本文已收錄至我的GitHub
背景
我們的故事要從一個風(fēng)和日麗的下午開始說起!
這天,外包韓在位置上寫代碼~外包韓根據(jù)如下定義
PO(persistant object): 持久化對象,可以看成是與數(shù)據(jù)庫中的表相映射的 java 對象。最簡單的 PO 就是對應(yīng)數(shù)據(jù)庫中某個表中的一條記錄。 VO(view object): 視圖對象,用于展示層,它的作用是把某個指定頁面(或組件)的所有數(shù)據(jù)封裝起來。 BO(business object): 業(yè)務(wù)對象,主要作用是把業(yè)務(wù)邏輯封裝為一個對象。這個對象可以包括一個或多個其它的對象。 DTO、DO(省略......)
將Bean進(jìn)行逐一分類!例如一個car_tb的表,于是他有了兩個類,一個叫CarPo,里頭屬性和表字段完全一致。另一個叫CarVo,用于頁面上的Car顯示!但是外包韓在做CarPo到CarVo轉(zhuǎn)換的時候,代碼是這么寫的,偽代碼如下:
CarPo carPo = this.carDao.selectById(1L);CarVo carVo = new CarVo();carVo.setId(carPo.getId());carVo.setName(carPo.getName());//省略一堆return carVo;
畫外音:看到這一串代碼是不是特別親切,我接手過一堆外包留下的代碼,就是這么寫的,一坨屎山!一類幾千行,一半都在set屬性。
恰巧,阿雄打水路過!雞賊的阿雄瞄了一眼外包韓的屏幕,看到外包韓的這一系列代碼!上去進(jìn)行一頓教育,覺得不夠優(yōu)雅!阿雄覺得,應(yīng)該用BeanUtils.copyProperties來簡化書寫,像下面這樣!
CarPo carPo = this.carDao.selectById(1L);CarVo carVo = new CarVo();BeanUtils.copyProperties(carPo, carVo);return carVo;
可是,外包韓盯著這段代碼,說道:"網(wǎng)上不是說反射效率慢,你這么寫,沒有性能問題么?"?
阿雄說道:" 如果是用Apache的BeanUtil類,確實有很大的性能問題,像阿里巴巴的代碼掃描插件,都禁止用該類,如下所示!"

"但是,如果采用的是像Spring的BeanUtils類,要在調(diào)用次數(shù)足夠多的時候,你才能明顯的感受到卡頓。"阿雄補(bǔ)充道。
"哇,阿雄真棒!"外包韓興奮不已!
看著這辦公室基情滿滿的氛圍。一旁正在拖地的清潔工------掃地?zé)?/strong>,他決定不再沉默。
只見掃地?zé)熑拥羰种械耐习眩蒙恼f道"我們不考慮性能。從拓展性角度看看!BeanUtils還是有很多問題的!"
復(fù)制對象時字段類型不一致,導(dǎo)致賦值不上,你怎么解決?自己拓展? 復(fù)制對象時字段名稱不一致,例如CarPo里叫carName,CarVo里叫name,導(dǎo)致賦值不上,你怎么解決?自己拓展? 如果是集合類的復(fù)制,例如List 轉(zhuǎn)換為List > ,你怎么處理? (省略一萬字....)
"那應(yīng)該怎么辦呢?"聽了掃地?zé)煹拿枋觯獍n疑惑的問道!
"很簡單,其實我們在轉(zhuǎn)換bean的過程中,set這些邏輯是固定的,唯一變化的就是轉(zhuǎn)換規(guī)則。因此,如果我們只需要書寫轉(zhuǎn)換規(guī)則,轉(zhuǎn)換代碼由系統(tǒng)根據(jù)規(guī)則自動生成,就方便很多了!還是用上面的例子,CarPo里叫carName,CarVo里叫name,屬性名稱不一致。我們就通過一個注解
@Mapping(source?=?"carName",?target?=?"name")指定對應(yīng)轉(zhuǎn)換規(guī)則。系統(tǒng)識別到這個注解,就會生成代碼
carVo.setName(carPo.getCarName())如果能以這樣的方式,set代碼由系統(tǒng)自動生成,那么靈活性將大大加強(qiáng),而且這種方式不存在性能問題!"掃地?zé)熝a(bǔ)充道!
"那這些set邏輯,由什么工具來生成呢?"外包韓和阿雄一起問道!
"嗯,這個工具的名字叫MapStruct!"
ok,上面的故事到了這里,就結(jié)束了!不需要問結(jié)局,結(jié)局只有一個,外包韓和阿雄幸福美滿的...(省略10000字)...
那么我們開始具體來說一說MapStruct!
MapStruct的教程
這里從用法、原理、優(yōu)勢三個角度來介紹一下這個插件,至于詳細(xì)教程,還是看官方文檔吧。
用法
引入pom文件如下
<dependency><groupId>org.mapstructgroupId><artifactId>mapstruct-jdk8artifactId><version>1.2.0.Finalversion>dependency><dependency><groupId>org.mapstructgroupId><artifactId>mapstruct-processorartifactId><version>1.2.0.Finalversion>dependency>
在準(zhǔn)備兩個實體類,為了方便演示,用了lombok插件。準(zhǔn)備兩個實體類,一個是CarPo
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class CarPo {private Integer id;private String brand;}
還有一個是CarVo
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class CarVo {private Integer id;private String brand;}
再來一個轉(zhuǎn)換接口
public interface CarCovertBasic {CarCovertBasic INSTANCE =Mappers.getMapper(CarCovertBasic.class);CarVo toConvertVo(CarPo source);}
測試代碼如下:
//實際中從數(shù)據(jù)庫取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的屬性值復(fù)制給了carVo。當(dāng)然,在這種情況下,功能和BeanUtils是差不多的,體現(xiàn)不出優(yōu)勢!嗯,我們放在后面說,我們先來說說原理!
原理
其實原理就是MapStruct插件會識別我們的接口,生成一個實現(xiàn)類,在實現(xiàn)類中,為我們實現(xiàn)了set邏輯!例如,上面的例子中,給CarCovertBasic接口,生成了一個實現(xiàn)類CarCovertBasicImpl,我們可以用反編譯工具看到源碼如下圖所示

下面,我們來說說優(yōu)勢
優(yōu)勢
(1)兩個類型屬性不一致
此時CarPo的一個屬性為carName,而CarVo對應(yīng)的屬性為name!
我們在接口上增加對應(yīng)關(guān)系即可,如下所示
public interface CarCovertBasic {CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);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已經(jīng)能識別到carPo中的carName屬性,并賦值成功。反編譯的圖如下

畫外音:如果有多個映射關(guān)系可以用@Mappings注解,嵌套多個@Mapping注解實現(xiàn),后文說明!
(2)集合類型轉(zhuǎn)換
如果我們要從List
public interface CarCovertBasic {CarCovertBasic INSTANCE = Mappers.getMapper(CarCovertBasic.class);(source = "carName", target = "name")CarVo toConvertVo(CarPo source);ListtoConvertVos(List ;source) }
如代碼所示,我們增加了一個toConvertVos方法即可,mapStruct生成代碼的時候,會幫我們?nèi)パh(huán)調(diào)用toConvertVo方法,給大家看一下反編譯的代碼,就一目了然

(3)類型不一致
在CarPo加一個屬性為Date類型的createTime,而在CarVo加一個屬性為String類型的createTime,那么代碼如下
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class CarPo {private Integer id;private String brand;private String carName;private Date createTime;}@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class CarVo {private Integer id;private String brand;private String name;private String createTime;}
接口就可以這么寫
@Mapperpublic 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<CarVo> toConvertVos(List<CarPo> source);}
這樣在代碼中,就能解決類型不一致的問題!在生成set方法的時候,自動調(diào)用DateUtil類進(jìn)行轉(zhuǎn)換,由于比較簡單,我就不貼反編譯的圖了!
(4)多對一
在實際業(yè)務(wù)情況中,我們有時候會遇到將兩個Bean映射為一個Bean的情況,假設(shè)我們此時還有一個類為AtrributePo,我們要將CarPo和AttributePo同時映射為CarBo,我們可以這么寫
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class AttributePo {private double price;private String color;}@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class CarBo {private Integer id;private String brand;private String carName;private Date createTime;private double price;private String color;}
接口改變?nèi)缦?/p>@Mapperpublic 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 CarBo toConvertBo(CarPo source1, AttributePo source2);}
直接增加接口即可,插件在生成代碼的時候,會幫我們自動組裝,看看下面的反編譯代碼就一目了然。

(5)其他
關(guān)于MapStruct還有其他很多的高級功能,我就不一一介紹了。大家可以參考下面的文檔,在用到的時候自行翻閱即可!文檔地址:https://mapstruct.org/documentation/reference-guide/
原創(chuàng)電子書原創(chuàng)思維導(dǎo)圖
掃碼或微信搜 Java3y?回復(fù)「888」領(lǐng)取1000+頁原創(chuàng)電子書和思維導(dǎo)圖。
![]() |
|





