高效對象屬性復制工具

日常編程中,經常會碰到對象屬性復制的場景,比如 VO、DTO、PO、VO 等之間的轉換,關于什么是VO、DTO、PO、VO 等可以看上篇文章,VO、DTO、PO、VO 等對象具體有哪些方式可以使用呢?

set/get 方式

性能最好的方式,但是當類的屬性數量只有簡單的幾個,通過手寫set/get即可完成,但是屬性有十幾個,甚至幾十個的時候,通過set/get的方式,可能會占用大量的編程時間,關鍵是像這樣的代碼,基本上是機械式的操作。面對這種重復又枯燥的編程工作,可以使用一些通用的對象屬性復制工具,常用的有如下幾種

ApacheBeanUtils

Apache 提供的一個用于 bean 拷貝的工具,早期使用的非常廣泛,使用上也非常簡單,首先項目中導入 apache beanutils 包,如下

<!--Apache BeanUtils-->
<dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version>
</dependency>

然后直接使用工具類即可,如下

// 原始對象
UserInfo source = new UserInfo();
// set...// 目標對象
UserInfo target = new UserInfo();
BeanUtils.copyProperties(target, source);
System.out.println(target.toString());

Apache BeanUtils 工具從操作使用上還是非常方便的,不過其底層源碼為了追求完美,加了過多的包裝,使用了很多反射,做了很多校驗,導致屬性復制時性能較差,因此阿里巴巴開發手冊上強制規定避免使用 Apache BeanUtils

SpringBeanUtils

spring 提供的一個 bean 轉換工具,首先項目中導入依賴

<!--spring BeanUtils-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>4.3.30.RELEASE</version>
</dependency>

在代碼中直接導入org.springframework.beans.BeanUtils工具進行對象屬性復制

/*** 對象屬性拷貝 <br>* 將源對象的屬性拷貝到目標對象** @param source 源對象* @param target 目標對象*/
public static void copyProperties(Object source, Object target) {try {BeanUtils.copyProperties(source, target);} catch (BeansException e) {LOGGER.error("BeanUtil property copy  failed :BeansException", e);} catch (Exception e) {LOGGER.error("BeanUtil property copy failed:Exception", e);}
}

初次之外,spring BeanUtils 還提供了重載方法

public static void copyProperties(Object source, Object target, String... ignoreProperties);

如果不想某些屬性復制過去,可以使用如下方式實現

BeanUtils.copyProperties(source, target, "userPwd");

也可以實現 List 集合之間的對象屬性賦值

/*** @param input 輸入集合* @param clzz  輸出集合類型* @param <E>   輸入集合類型* @param <T>   輸出集合類型* @return 返回集合*/
public static <E, T> List<T> convertList2List(List<E> input, Class<T> clzz) {List<T> output = Lists.newArrayList();if (CollectionUtils.isNotEmpty(input)) {for (E source : input) {T target = BeanUtils.instantiate(clzz);BeanUtil.copyProperties(source, target);output.add(target);}}return output;
}@RunWith(PowerMockRunner.class)
public class TestUtil
{@Testpublic void test(){Employee ee1=new Employee("A",33,"abc");Employee ee2=new Employee("B",44,"abcd");User user=new User();BeanUtil.copyProperties(ee1, user);System.out.println(user);List<User> output=new ArrayList<>();List<Employee> source= Arrays.asList(ee1,ee2);output=BeanUtil.convertList2List(source,User.class);for (User str:output) {System.out.println(str);}}
}

雖然Apache BeanUtils和Spring BeanUtils使用起來都很方便,但是兩者性能差異非常大,Spring BeanUtils的對象屬性復制速度比Apache BeanUtils要快很多,主要原因在于 Spring 并沒有像 Apache 一樣使用反射做過多的參數校驗,Spring BeanUtils的實現原理也比較簡答,就是通過Java的Introspector獲取到兩個類的PropertyDescriptor,對比兩個屬性具有相同的名字和類型,如果是,則進行賦值(通過ReadMethod獲取值,通過WriteMethod賦值),否則忽略。為了提高性能Spring對BeanInfo和PropertyDescriptor進行了緩存,源碼如下(4.3.9 版本)

/**  * Copy the property values of the given source bean into the given target bean.  * <p>Note: The source and target classes do not have to match or even be derived  * from each other, as long as the properties match. Any bean properties that the  * source bean exposes but the target bean does not will silently be ignored.  * @param source the source bean  * @param target the target bean  * @param editable the class (or interface) to restrict property setting to  * @param ignoreProperties array of property names to ignore  * @throws BeansException if the copying failed  * @see BeanWrapper  */  
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)  
throws BeansException {  Assert.notNull(source, "Source must not be null");  Assert.notNull(target, "Target must not be null");  Class<?> actualEditable = target.getClass();  if (editable != null) {  if (!editable.isInstance(target)) {  throw new IllegalArgumentException("Target class [" + target.getClass().getName() +  "] not assignable to Editable class [" + editable.getName() + "]");  }  actualEditable = editable;  }  //獲取target類的屬性(有緩存)  PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);  List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);  for (PropertyDescriptor targetPd : targetPds) {  Method writeMethod = targetPd.getWriteMethod();  if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {  //獲取source類的屬性(有緩存)  PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());  if (sourcePd != null) {  Method readMethod = sourcePd.getReadMethod();  if (readMethod != null &&  //判斷target的setter方法入參和source的getter方法返回類型是否一致  ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {  try {  if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {  readMethod.setAccessible(true);  }  //獲取源值  Object value = readMethod.invoke(source);  if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {  writeMethod.setAccessible(true);  }  //賦值到target  writeMethod.invoke(target, value);  }  catch (Throwable ex) {  throw new FatalBeanException(  "Could not copy property '" + targetPd.getName() + "' from source to target", ex);  }  }  }  }  }  
}  

還有一個需要注意的地方是,Apache BeanUtils和Spring BeanUtils的類名和方法基本上相同,但是它們的原始對象和目標對象的參數位置是相反的,如果直接從Apache BeanUtils切換到Spring BeanUtils有巨大的風險

雖然使用起來很方便,但是有幾個坑得注意

1、類型不匹配

@Data
public class SourceBean {private Long age;
}@Data
public class TargetBean {private String age;
}public class Test {public static void main(String[] args) {SourceBean source = new SourceBean();source.setAge(25L);TargetBean target = new TargetBean();BeanUtils.copyProperties(source, target);System.out.println(target.getAge());  //拷貝賦值失敗,輸出null}
}

2、是淺拷貝

什么是深拷貝?什么是淺拷貝?

● 淺拷貝是指創建一個新對象,該對象的屬性值與原始對象相同,但對于引用類型的屬性,仍然共享相同的引用。換句話說,淺拷貝只復制對象及其引用,而不復制引用指向的對象本身。

● 深拷貝是指創建一個新對象,該對象的屬性值與原始對象相同,包括引用類型的屬性。深拷貝會遞歸復制引用對象,創建全新的對象,以確保拷貝后的對象與原始對象完全獨立

public class Address {private String city;//getter 和 setter 方法省略
}public class Person {private String name;private Address address;//getter 和 setter 方法省略
}Person sourcePerson = new Person();
sourcePerson.setName("John");
Address address = new Address();
address.setCity("New York");
sourcePerson.setAddress(address);Person targetPerson = new Person();
BeanUtils.copyProperties(sourcePerson, targetPerson);sourcePerson.getAddress().setCity("London");System.out.println(targetPerson.getAddress().getCity());  // 輸出為 "London"

3、屬性名稱不一致

public class SourceBean {private String username;// getter 和 setter 方法省略
}public class TargetBean {private String userName;// getter 和 setter 方法省略
}SourceBean source = new SourceBean();
source.setUsername("男孩");TargetBean target = new TargetBean();
BeanUtils.copyProperties(source, target);System.out.println(target.getUserName());   // 輸出為 null

4、null 覆蓋

Hutool BeanUtil

hutool是平常使用比較頻繁的一個工具包,對文件、加密解密、轉碼、正則、線程、XML等JDK方法進行封裝,并且也可以進行對象的拷貝。在使用前引入坐標:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version>
</dependency>

使用方式

BeanUtil.copyProperties(UserPo,UserDto);

也可以忽略指定的屬性

void copyProperties(Object source, Object target, String... ignoreProperties);

除此之外,hutool的BeanUtil還提供了很多其他實用的方法

Cglib BeanCopier

Cglib BeanCopier 對象屬性復制工具,首先項目中導入 cglib 包

<!--cglib-->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

然后在代碼中直接導入net.sf.cglib.beans.BeanCopier工具進行對象屬性復制,樣例代碼如下:

// 原始對象
UserInfo source = new UserInfo();
// set...// 獲取一個復制工具
BeanCopier beanCopier = BeanCopier.create(UserInfo.class, UserInfo.class, false);// 對象屬性值復制
UserInfo target = new UserInfo();
beanCopier.copy(source, target, null);
System.out.println(target.toString());

如果遇到字段名相同,但是類型不一致的對象復制,可以引入轉換器,進行類型轉換,比如這樣:

UserInfo source = new UserInfo();
// set...// 創建一個復制工具
BeanCopier beanCopier = BeanCopier.create(UserInfo.class, UserInfo.class, true);// 自定義對象屬性值復制
UserInfo target = new UserInfo();
beanCopier.copy(source, target, new Converter() {@Overridepublic Object convert(Object source, Class target, Object context) {if(source instanceof Integer){return String.valueOf(source);}return source;}
});
System.out.println(target.toString());

Cglib BeanCopier 的工作原理與 apache Beanutils 和 spring beanutils 原理不太一樣,其主要使用字節碼技術動態生成一個代理類,通過代理類來實現get/set方法。

雖然生成代理類過程存在一定開銷,但是一旦生成可以重復使用,因此 Cglib 性能相比以上兩種 Beanutils 性能都要好。另外就是,如果工程是基于 Spring 框架開發的,查找 BeanCopier 這個類的時候,可以發現兩個不同的包,一個屬于Cglib,另一個屬于Spring-Core。

其實Spring-Core內置的BeanCopier引入了 Cglib 中的類,這么做的目的是為保證 Spring 中使用 Cglib 相關類的穩定性,防止外部 Cglib 依賴不一致,導致 Spring 運行異常,因此無論你引用那個包,本質都是使用 Cglib

MapStuct

MapStruct官網:MapStruct – Java bean mappings, the easy way!

MapStruct官網示例:https://github.com/mapstruct/mapstruct-examples

MapStruct 也是一款對象屬性復制的工具,但是它跟上面介紹的幾款工具技術實現思路都不一樣,主要區別在于無論是Beanutils還是BeanCopier,都是程序運行期間去執行對象屬性復制操作。而MapStruct是在程序編譯期間,就已經生成好了對象屬性復制相關的邏輯。因此可以想象的到,MapStruct的復制性能要快很多!MapStruct工具的使用參考第二篇文章

總結

幾種方式性能測試對比

以上幾種對象屬性復制的方式

  1. 如果當前類只有簡單的幾個屬性,建議直接使用set/get,原生編程性能最好
  2. 如果類屬性很多,可以使用Spring BeanUtils或者Cglib BeanCopier工具,可以省下很多的機械式編程工作
  3. 如果當前類屬性很多,同時對復制性能有要求,推薦使用MapStruct

最后,以上的對象屬性復制工具都是淺拷貝的實現方式,如果要深拷貝,可以使用對象序列戶和反序列化技術實現!

更多文章:https://codeyb.top/

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95468.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95468.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95468.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大疆圖傳技術參數對比 你了解多少?

無人機是現代航空技術與智能控制技術結合的產物&#xff0c;已從軍事領域廣泛滲透至民用場景&#xff0c;成為推動各行業效率升級的關鍵工具。無人機的全稱為 “無人駕駛航空器&#xff08;Unmanned Aerial Vehicle&#xff0c;簡稱 UAV&#xff09;”&#xff0c;簡言之&#…

Redis 緩存熱身(Cache Warm-up):原理、方案與實踐

在 Redis 緩存架構中&#xff0c;“緩存熱身”是指在系統正式提供服務前&#xff08;如重啟、擴容后&#xff09;&#xff0c;主動將熱點數據加載到 Redis 中的操作。其核心目標是避免**緩存穿透**&#xff08;請求直達數據庫&#xff09;和**緩存雪崩**&#xff08;大量請求同…

基于SpringBoot的大學生就業招聘系統

1. 在線演示&#xff1a; 后臺&#xff1a;http://springbootiv1oo.xiaobias.com/springbootiv1oo/admin/dist/index.html 前臺&#xff1a;http://springbootiv1oo.xiaobias.com/springbootiv1oo/front/index.html 管理員&#xff1a;abo/abo 用戶&#xff1a;用戶1/123456、…

Java反序列化漏洞揭秘:從原理到攻擊實戰

一、背景 熟悉接口開發的同學一定知道&#xff0c;能將數據對象很輕松的實現多平臺之間的通信、對象持久化存儲&#xff0c;序列化和反序列化是一種非常有效的手段&#xff0c;例如如下應用場景&#xff0c;對象必須 100% 實現序列化。 DUBBO&#xff1a;對象傳輸必須要實現序…

Time-MOE 音頻序列分類任務

prompt 我準備做語音疾病分類任務。語音音頻是 WAV 格式的音頻&#xff0c;基本上分為兩類&#xff0c;分別是疾病類和非疾病類。也有少數數據集是多分類&#xff0c;現在我找到了26個數據集&#xff0c;我準備我已經在 MLP CNN 上面測試了它們的基準&#xff0c;下面我找到了一…

[嵌入式embed][Qt]Qt5.12+Opencv4.x+Cmake4.x_測試Qt編譯的opencv4.x的庫

[嵌入式embed][Qt]Qt5.12Opencv4.xCmake4.x_測試Qt編譯的opencv4.x的庫編譯Qt-Opencv庫測試流程-①創建一個簡單的qt-ui工程配置 & 測試配置庫編譯環境測試代碼百度云-工程(opencv4.xqt5.12的工程)參考文檔編譯Qt-Opencv庫 [嵌入式embed][Qt]Qt5.12Opencv4.xCmake4.x_用Qt…

相較于傳統AR礦物鑒定有哪些優勢?

與傳統的礦物鑒定方法相比&#xff0c;AR礦物鑒定就像是一位全面升級的“超級助手”&#xff0c;展現出了無可比擬的優勢。傳統的礦物鑒定方法&#xff0c;往往依賴于地質學家或專業鑒定人員的豐富經驗。他們需要通過肉眼觀察礦物的顏色、光澤、硬度等物理特征&#xff0c;再結…

第5節:分布式文件存儲

本節主要是講解的是分布式文件存儲&#xff0c;主要介紹了阿里云OSS云存儲和Minio文件存儲&#xff0c;本章重點主要是掌握怎么在SpringBoot項目里面接入文件存儲。 記錄、交流、實踐&#xff0c;讓每一份付出皆可看見&#xff0c;讓你我共同前行&#x1f601; 1.分布式文件存…

當 GitHub 宕機時,我們如何協作?

一、引言1.1 GitHub 的重要性及宕機影響在當今軟件開發的生態系統中&#xff0c;GitHub 已然成為全球開發者不可或缺的核心平臺。它為無數開源項目與企業級開發團隊提供了高效的代碼托管、版本控制、協作開發以及項目管理等服務。然而&#xff0c;2025 年 8 月那場波及全球的 G…

Ansible 常用模塊歸納總結

[studentmaster ansible]$ ansible-galaxy collection install http://ansible.example.com/materials/community-general-6.3.0.tar.gz -p collections/##將第三方模塊下載到collections下 [studentmaster ansible]$ ansible-galaxy collection install http://ansible.exampl…

計算機網絡:概述層---TCP/IP參考模型

&#x1f310; TCP/IP四層模型詳解&#xff1a;互聯網的核心協議架構深度剖析 &#x1f4c5; 更新時間&#xff1a;2025年9月3日 &#x1f3f7;? 標簽&#xff1a;TCP/IP模型 | 互聯網協議 | 四層模型 | 計算機網絡 | 協議棧 | 網絡通信 | 王道考研 摘要: 本文將深入淺出地解析…

打工人日報#20250902

打工人日報#20250902 今天晚上去了玄武湖&#xff0c;來南京三次了&#xff0c;終于來了一次知識點 不確定度 “不確定度” 是測量領域的核心概念&#xff0c;用于量化測量結果的可靠性與分散程度—— 簡單來說&#xff0c;它回答了 “這個測量值有多可信&#xff1f;真實值可能…

告別手動復制粘貼:C# 實現 Excel 與 TXT 文本文件高效互轉

在日常辦公和數據處理工作中&#xff0c;Excel 和 TXT文本文件是兩種常見的數據存儲格式。Excel文件適合進行復雜的數據分析、公式運算和圖表生成&#xff0c;而 TXT文件則更適合用于存儲和傳輸純文本數據&#xff0c;如日志、配置文件或簡單的數據列表。很多時候&#xff0c;我…

elasticsearch學習(二)插件安裝

目錄上一篇文章查看插件安裝分詞器analysis-icu重啟實例重新查看插件上一篇文章 elasticsearch學習&#xff08;一&#xff09; 下載、安裝和初次部署 查看插件 ? bin elasticsearch-plugin list warning: ignoring JAVA_HOME/Library/Java/JavaVirtualMachines/jdk1.8.0_…

(原創)SAP ATP可用量檢查 OPJJ功能配置說明(900+字!)

前言&#xff1a;經常在ATP遇到問題&#xff0c;每次上網找都沒有相關資料&#xff0c;一氣之下直接在官網找資料收集&#xff0c;已整理相關字段與大家分享&#xff0c;避免大家走彎路附上我個人很久之前的的測試結果&#xff1a;具體字段控制說明檢查不考慮補貨提前期關聯字段…

Unity資源管理——操作一覽(編輯器下 運行時)

本文由 NRatel 歷史筆記整理而來&#xff0c;如有錯誤歡迎指正。 資源管理是Unity游戲開發中的重頭工作之一。 以下按【編輯器下】和 【運行時】&#xff0c;共十多個步驟&#xff0c;一覽總體流程&#xff08;內容巨大&#xff0c;不細展開&#xff09;。 一、資源導入Unity【…

Sentinel vs Resilience4j vs Bucket4j:分布式限流方案對比與實戰

Sentinel vs Resilience4j vs Bucket4j&#xff1a;分布式限流方案對比與實戰 在高并發微服務架構中&#xff0c;合理的限流策略是保護系統穩定性與可用性的關鍵。本文將從問題背景入手&#xff0c;對 Sentinel、Resilience4j 和 Bucket4j 三種常見的分布式限流方案進行對比&am…

Spring Boot 3.5.3 集成 Log4j2 日志系統

在 Spring Boot 3.5.3 中&#xff0c;要將默認的 Logback 替換為 Log4j2&#xff0c;需要以下步驟&#xff1a;1. 添加 Log4j2 依賴在 pom.xml中排除默認的 Logback 依賴并添加 Log4j2 依賴&#xff1a;<dependencies><!-- 排除默認的 Logback --><dependency&g…

ADB圖片上傳輪播

可以通過ADB在機器中進行上傳照片&#xff0c;進行其他圖片播放 當前系統架構分析 1. 現有組件結構 ImageCarouselActivity: 主要的輪播Activity&#xff0c;繼承自BaseBindingActivity 實現全屏顯示和沉浸式體驗使用ViewPager2進行圖片輪播支持自動輪播&#xff08;5秒間隔&…

異常處理小妙招——2.代碼的韌性:如何實現操作的原子性回滾

一、核心思想&#xff1a;什么叫“失敗原子性”&#xff1f; 想象一下你在玩一個闖關游戲&#xff0c;有一關需要你連續跳過三個平臺。 不具有原子性&#xff1a;你跳過了第一個和第二個平臺&#xff0c;但在跳第三個時失敗了、掉下去了。結果你不僅沒過關&#xff0c;連之前跳…