介面超時日誌排查分析-BeanUtils物件複製6秒及型別不一致複製異常,複製null屬性被覆蓋解決,常見Bean複製框架的效能對比
1.介面超時日誌排查分析-BeanUtils物件複製6秒
1.查詢日誌命令,分析介面的請求及響應的時長
cat proJectDock.log | grep -E "請求開始時間|請求正常消耗時間" >> dock1.log
2.org.springframework.beans.BeanUtils
org.springframework.beans.BeanUtils 通常用於Java Spring框架中的物件複製。如果你在使用 BeanUtils 花費了6秒鐘,這可能是因為以下幾個原因:
2.1 複製的物件太大,包含大量的屬性,導致處理時間較長。
2.2 被複製的物件中可能包含複雜的資料結構或者迴圈引用,導致遞迴時間增加。
2.3 如果涉及到JDBC等資料庫操作,可能是查詢資料的時間較長。
3.本地專案中,將日誌列印的json串複製到本地測試類,json轉物件,然後物件複製。未復現。
BeanUtils.copyProperties(req,orderReqVO);
請求 req= json串
4.型別不一致複製異常,複製null屬性被覆蓋解決demo
package com.example.core.mydemo.BeanUtils; import java.util.Date; import java.util.List; public class A { private String name; private List<Integer> ids; private Integer age; private Date regDate; public Date getRegDate() { return regDate; } public void setRegDate(Date regDate) { this.regDate = regDate; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Integer> getIds() { return ids; } public void setIds(List<Integer> ids) { this.ids = ids; } } package com.example.core.mydemo.BeanUtils; import java.util.Date; import java.util.List; public class B { private String name; private List<String> ids; private Integer age; private Date regDate; public Date getRegDate() { return regDate; } public void setRegDate(Date regDate) { this.regDate = regDate; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getIds() { return ids; } public void setIds(List<String> ids) { this.ids = ids; } } package com.example.core.mydemo.BeanUtils; import com.alibaba.fastjson.JSON; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.cglib.beans.BeanCopier; import java.util.*; public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); // first.setName("demo"); first.setName(null); // first.setAge(null); // first.setIds(Arrays.asList(1, 2, 3)); first.setRegDate(new Date()); B second = new B(); second.setName("優質"); // second.setAge(10); /** * //存在的問題: * //會將源物件中欄位為null的值,覆蓋到目標有 值欄位。最終目標物件中的物件值被覆蓋,也為null * second={"regDate":1733455040911} * second date=Fri Dec 06 11:17:20 CST 2024 */ BeanUtils.copyProperties(first, second); System.out.println("second=" + JSON.toJSONString(second)); System.out.println("second date=" + second.getRegDate()); second.setName("優質"); /** * 解決null被覆蓋的情況 - "ignoreProperties" * str陣列=[Ljava.lang.String;@7e0ea639 * second 非空={"name":"優質","regDate":1733454983304} */ BeanUtils.copyProperties(first, second, getNullPropertyNames(first)); System.out.println("second 非空=" + JSON.toJSONString(second)); /** * Exception in thread "main" java.lang.NullPointerException * at com.example.core.mydemo.BeanUtils.BeanUtilDemo.main(BeanUtilDemo.java:16) */ BeanUtils.copyProperties(first, second); for (String each : second.getIds()) {// 型別轉換異常 System.out.println(each); } /** * Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String * at com.example.core.mydemo.BeanUtils.BeanUtilDemo.main(BeanUtilDemo.java:23) */ // final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false); // beanCopier.copy(first,second,null); // // for (String each : second.getIds()) {// 型別轉換異常 // System.out.println(each); // } } public static String[] getNullPropertyNames (Object source) { final BeanWrapper src = new BeanWrapperImpl(source); java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set<String> emptyNames = new HashSet<String>(); for(java.beans.PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if(srcValue == null) { emptyNames.add(pd.getName()); } } String[] result = new String[emptyNames.size()]; String[] str = emptyNames.toArray(result); System.out.println("str陣列=" + str); return str; } }
工具類都是對bean之間存在屬性名相同的屬性進行處理,無論是源bean或者是目標bean中多出來的屬性均不處理。
BeanUtils對部分屬性不支援null,具體如下:
a. java.util.Date型別不支援,但是它的子類java.sql.Date是被支援的。java.util.Date直接copy會報異常; >> 驗證不會
b. Boolean,Integer,Long等不支援,會將null轉化為0; >> 驗證不會
c. String支援,轉化後依然為null。 >>驗證會
BeanUtils支援的轉換型別如下
* java.lang.BigDecimal
* java.lang.BigInteger
* boolean and java.lang.Boolean
* byte and java.lang.Byte
* char and java.lang.Character
* java.lang.Class
* double and java.lang.Double
* float and java.lang.Float
* int and java.lang.Integer
* long and java.lang.Long
* short and java.lang.Short
* java.lang.String
* java.sql.Date
* java.sql.Time
* java.sql.Timestamp
3.常見Bean複製框架的效能對比
由於 Java 的泛型其實是編譯期檢查,編譯後泛型擦除,導致執行時 List<Integer> 和 List<String> 都是 List 型別,可以正常賦值。這就導致在使用很多屬性對映工具時,編譯時不容易明顯的錯誤。
因此慎用屬性轉換工具,如果可能建議自定義轉換類,使用 IDEA外掛自動填充,效率也挺高, A 或 B 中任何屬性型別不匹配,甚至刪除一個屬性,編譯階段即可報錯,而且直接呼叫 get set 的效率也是非常高的。
1.apache BeanUtils 阿里規範中,明確說明了,不要使用它
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency> //關鍵方法 BeanUtils.copyProperties(res, source);
2.cglib BeanCopier cglib是透過動態代理的方式來實現屬性複製的,與上面基於反射實現方式存在本質上的區別,這也是它效能更優秀的主因
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.8.RELEASE</version> <scope>compile</scope> </dependency> 或者 <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> //關鍵方法 BeanCopier copier = BeanCopier.create(source.getClass(), target, false); copier.copy(source, res, null);
3.spring BeanUtils 基於內省+反射,藉助getter/setter方法實現屬性複製,效能比apache高
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.1.RELEASE</version> <scope>compile</scope> </dependency> //關鍵方法 BeanUtils.copyProperties(source, res);
4. hutool BeanUtil hutool 提供了很多的java工具類,從測試效果來看它的效能比apache會高一點,當低於spring
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.6.0</version> </dependency> //關鍵方法 BeanUtil.toBean(source, target);
5. MapStruct MapStruct 效能更強悍了,缺點也比較明顯,需要宣告bean的轉換介面,自動程式碼生成的方式來實現複製,效能媲美直接的get/set
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.4.2.Final</version> </dependency> //關鍵方法 @Mapper //宣告bean的轉換介面 public interface MapStructCopier { Target copy(Source source); } @Component public class MapsCopier { private MapStructCopier mapStructCopier = Mappers.getMapper(MapStructCopier.class); public Target copy(Source source, Class<Target> target) { return mapStructCopier.copy(source); } }