介面超時日誌排查分析-BeanUtils物件複製6秒及型別不一致複製異常,複製null屬性被覆蓋解決,常見Bean複製框架的效能對比

oktokeep發表於2024-12-09

介面超時日誌排查分析-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);
    }
}

相關文章