六種常用Bean拷貝工具一覽
在我們日常的工作中,經常需要做物件的拷貝或轉化,例如在傳遞引數時,把入參的DTO轉化為PO存入資料庫,在返回前端時把PO再轉化為VO。如果再分的細一點,可能還會有DO(Domain Object),TO(Transfer Object) ,BO(business object)等物件,隨著業務的劃分越來越細,物件的拷貝工作也越來越頻繁,所以本文就來梳理一下常用的物件拷貝工具和它們的差異。
常用的工具大概有以下幾種:
- Apache BeanUtils
- Spring BeanUtils
- cglib BeanCopier
- Hutool BeanUtil
- Mapstruct
- Dozer
準備工作,建立兩個類PO和DTO:
@Data public class OrderPO { Integer id; String orderNumber; List<String> proId; } @Data public class OrderDTO { int id; String orderNumber; List<String> proId; }
01.Apache BeanUtils
引入依賴座標:
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
進行測試,初始化PO物件,並建立DTO空物件,使用BeanUtils進行:
@org.junit.Test public void test(){ OrderPO orderPO=new OrderPO(); orderPO.setId(1); orderPO.setOrderNumber("orderNumber"); ArrayList<String> list = new ArrayList<String>() {{ add("1"); add("2"); }}; orderPO.setProId(list); OrderDTO orderDTO=new OrderDTO(); BeanUtils.copyProperties(orderDTO,orderPO); }
列印兩個物件,具有相同的屬性:
OrderPO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2]) OrderDTO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2])
可以看出,在Bean中具有相同名稱的屬性分別是基本資料型別和包裝類時,比如分別是int和Integer時,可以正常進行拷貝。那麼再深究一點,拷貝Bean過程中,使用的是深拷貝還是淺拷貝呢?
兩個List物件使用的是同一個物件,因此在拷貝中,如果存在引用物件,那麼使用的是淺拷貝。在完成拷貝後,如果再修改這個物件:
list.add("3"); log.info(orderDTO.getProId());
再次列印DTO物件,發現即使不再次重新拷貝,修改的值也會被新增過去
OrderDTO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2, 3])
02.Spring BeanUtils
如果使用的spring專案時不需要單獨引入依賴,單獨使用時需要引入座標:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.2.RELEASE</version> </dependency>
使用方式與apache的BeanUtils方法名相同,但引數順序相反,第一個引數是源物件,第二個引數是目標物件:
BeanUtils.copyProperties(orderPO,orderDTO);
過程省略,這裡使用的還是淺拷貝。spring的BeanUtils還提供了額外的方法,這個可變引數的方法可以忽略某些屬性進行拷貝:
void copyProperties(Object source, Object target, String... ignoreProperties);
忽略orderNumber屬性進行拷貝:
BeanUtils.copyProperties(orderPO,orderDTO,"orderNumber");
輸出結果:
OrderPO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2]) OrderDTO(id=1, orderNumber=null, proId=[1, 2])
此外,在阿里巴巴的開發手冊中,強制避免使用apache BeanUtils進行拷貝,建議使用Spring BeanUtils或下面要介紹的BeanCopier。主要原因還是在於Spring並沒有與 apache一樣對反射做了過多校驗,另外Spring BeanUtils內部使用了快取,加快轉換的速度。此外,由於我們的大多專案已經集成了Spring ,如果沒有其他特殊的需求,直接使用它的BeanUtils就能滿足我們的基本需求。
03.cglib BeanCopier
如果工程內含有spring-core包的依賴,也不需要額外引入依賴,否則需要引入座標:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
使用示例:
BeanCopier beanCopier = BeanCopier.create( orderPO.getClass(), orderDTO.getClass(), false); beanCopier.copy(orderPO,orderDTO,null);
測試結果:
OrderPO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2]) OrderDTO(id=0, orderNumberorderNumber=orderNumber, proId=[1, 2])
在上面的例子中,id欄位沒有被正常拷貝,兩個欄位不同的是在PO中使用的是包裝型別Integer,但DTO中使用的是基本型別int。因此,使用BeanCopier時,如果存在基本型別和包裝類,是無法被正常拷貝,改為相同型別後才能被正常拷貝。另外,BeanCopier使用的仍然是淺拷貝,驗證過程大家可以自己進行實驗。
04.Hutool BeanUtil
hutool是個人平常使用比較頻繁的一個工具包,對檔案、加密解密、轉碼、正則、執行緒、XML等JDK方法進行封裝,並且也可以進行物件的拷貝。在使用前引入座標:
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.1.0</version> </dependency>
使用方法如下,並且使用的也是淺拷貝方式:
BeanUtil.copyProperties(orderPO,orderDTO);
和Spring BeanUtils相同,也可以進行屬性的忽略:
void copyProperties(Object source, Object target, String... ignoreProperties);
除此之外,hutool的BeanUtil還提供了很多其他實用的方法:
個人在使用中感覺Bean和Map的互相轉換還是很常用的,有時在使用Map接收引數時,後期能夠很方便的把Map轉換為Bean
05.Mapstruct
Mapstruct的使用和上面幾種方式有些不同,因為上面的幾種方式,spring和apache,hutool使用的都是反射,cglib是基於位元組碼檔案的操作,都是在都程式碼執行期間動態執行的,但是Mapstruct不同,它在編譯期間就生成了 Bean屬性複製的程式碼,執行期間就無需使用反射或者位元組碼技術,所以具有很高的效能。
使用Mapstruct需要需要引入下面的依賴:
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>1.3.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.3.0.Final</version> </dependency>
需要額外寫一個介面來實現:
@Mapper public interface ConvertMapper { OrderDTO po2Dto(OrderPO orderPO); }
這裡的@Mapper註解不是用於mybatis的註解,而是org.mapstruct.Mapper。使用起來也非常簡單:
ConvertMapper mapper = Mappers.getMapper(ConvertMapper.class); OrderDTO orderDTO=mapper.po2Dto(orderPO);
檢視編譯後的target目錄,編譯時將我們定義的ConvertMapper 介面,生成了ConvertMapperImpl實現類,並實現了po2Dto方法。看一下編譯生成的檔案:
可以看到方法中為每一個屬性生成了set方法,並且對於引用物件,生成了一個新的物件,使用深拷貝的方式,所以修改之前的引用物件,這裡的值也不會改變。並且,這種使用set/get的方式比使用反射的速度更快。
06.Dozer
Dozer是一個Bean到Bean對映器,它以遞迴方式將資料從一個物件複製到另一個物件,並且這些Bean可以具有不同的複雜型別。使用前引入依賴座標:
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.4.0</version> </dependency>
呼叫方式非常簡單:
DozerBeanMapper mapper = new DozerBeanMapper(); OrderDTO orderDTO=mapper.map(orderPO,OrderDTO.class);
檢視執行時生成的物件,可以看見使用的深拷貝的方式:
除此之外,還可以配置不同屬性名稱的對映,修改DTO和PO,在PO中新增一個name屬性,在DTO中新增value屬性:
@Data public class OrderPO { Integer id; String orderNumber; List<String> proId; String name; } @Data public class OrderDTO { int id; String orderNumber; List<String> proId; String value; }
新建一個配置檔案,在mapping中可以新增欄位的對映關係:
<?xml version="1.0" encoding="UTF-8"?> <mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd"> <mapping> <class-a>com.cn.entity.OrderPO</class-a> <class-b>com.cn.entity.OrderDTO</class-b> <field> <a>name</a> <b>value</b> </field> </mapping> </mappings>
DozerBeanMapper使用上面的配置檔案進行配置,再次拷貝物件:
... orderPO.setName("hydra"); DozerBeanMapper mapper = new DozerBeanMapper(); List<String> mappingFiles = new ArrayList<>(); mappingFiles.add("dozer.xml"); mapper.setMappingFiles(mappingFiles); OrderDTO orderDTO=mapper.map(orderPO,OrderDTO.class);
檢視測試結果,不同名稱的欄位也可以進行拷貝了:
OrderPO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2], name=hydra) OrderDTO(id=1, orderNumberorderNumber=orderNumber, proId=[1, 2], value=hydra)
如果業務場景中的Bean具有很多不同的屬性,這麼配置起來還是很麻煩的,需要額外手寫很多xml檔案。以上就是工作中常被接觸到的幾種物件拷貝工具,在具體的使用中,更多的要結合拷貝效率等要求,以及工作場景中需要使用的是深拷貝還是淺拷貝等諸多因素。
【責任編輯:龐桂玉 TEL:(010)68476606】
- Spring中實現非同步呼叫的方式有哪些?
- 帶引數的全型別 Python 裝飾器
- 整理了幾個Python正則表示式,拿走就能用!
- 設計模式之狀態模式
- 如何實現資料庫讀一致性
- SOLID:開閉原則Go程式碼實戰
- React中如何引入CSS呢
- 慢查詢 MySQL 定位優化技巧,從10s優化到300ms
- 一個新視角:前端框架們都卷錯方向了?
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 手寫程式語言-遞迴函式是如何實現的?
- 一文搞懂模糊匹配:定義、過程與技術
- 新來個阿里 P7,僅花 2 小時,做出一個多執行緒永動任務,看完直接跪了
- Puzzlescript,一種開發H5益智遊戲的引擎
- @Autowired和@Resource到底什麼區別,你明白了嗎?
- “四招”守護個人資訊保安
- CSS transition 小技巧!如何保留 hover 的狀態?
- React如此受歡迎離不開這4個主要原則
- 我是怎麼入行做風控的
- 重溫三十年前對於 NN 的批判:神經網路無法實現可解釋 AI