Beancopier全方位詳解

一、Beancopier原理

Beancopier是一個Java實現的Bean拷貝工具,其原理是通過反射機制實現對Bean的屬性值的讀取和賦值。

它核心類是BeanCopier,通過源對象和目標對象的Class對象建立出一個BeanCopier實例,在調用copy方法時,實現對源對象屬性的拷貝到目標對象上。

public class BeanCopier {
    private static final Map BEAN_COPIERS = new ConcurrentHashMap();

    public static void copy(Object source, Object target) {
        String key = genKey(source.getClass(), target.getClass());
        BeanCopier copier = BEAN_COPIERS.get(key);
        if (copier == null) {
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            BEAN_COPIERS.put(key, copier);
        }
        copier.copy(source, target, null);
    }
}

可以看到,該實現方式對比其他Bean拷貝方式,具有很高的性能優勢,而且具有簡潔的API接口。

二、Beancopier使用converter

有時候在進行Bean的拷貝的時候,源對象的屬性與目標對象的屬性名稱和類型存在差異,因此需要進行相應的類型轉換,這時候就需要使用到Beancopier提供的Converter接口。

public interface Converter {
    Object convert(Object source, Class sourceClazz, Class targetClazz);
}

Converter接口僅有一個方法,用於實現源對象屬性到目標對象屬性的類型轉換,我們可以在創建BeanCopier對象的時候傳入一個Converter實例。

public class PersonConvert implements Converter {
    @Override
    public Object convert(Object source, Class sourceClazz, Class targetClazz) {
        if (source instanceof String && targetClazz == LocalTime.class) {
            return LocalTime.parse((String) source, DateTimeFormatter.ISO_LOCAL_TIME);
        }
        return source;
    }
}

public class Person {
    private String name;
    private int age;
    private String gender;
    private LocalTime createTime;

    // getter and Setter
}

public class PersonDTO {
    private String fullName;
    private int yearsOld;
    private String sex;
    private LocalDateTime createTime;

    // getter and Setter
}

public class BeancopierConverterTest {
    @Test
    public void testBeancopierWithConverter() {
        Person person = new Person();
        person.setName("Shack");
        person.setAge(34);
        person.setGender("male");
        person.setCreateTime(LocalTime.parse("03:18:37", DateTimeFormatter.ISO_LOCAL_TIME));

        PersonDTO personDTO = new PersonDTO();

        BeanCopier copier = BeanCopier.create(Person.class, PersonDTO.class, true);

        copier.copy(person, personDTO, new PersonConvert());

        assertThat(personDTO.getFullName()).isEqualTo("Shack");
        assertThat(personDTO.getYearsOld()).isEqualTo(34);
        assertThat(personDTO.getSex()).isEqualTo("male");
        assertThat(personDTO.getCreateTime()).isEqualTo(LocalDateTime.now().withNano(0));
    }
}

我們定義一個PersonConverter類實現Converter接口,實現將Person中的LocalTime類型屬性值轉換到PersonDTO的LocalDateTime類型屬性上。

在調用BeanCopier的copy方法時,將PersonConvert實例傳入即可實現局部的類型轉換。

三、Beancopier反射性能

既然Beancopier是基於反射機制實現的,那麼其性能是否優於其他的Bean拷貝機制呢?

考慮到Java編譯器的優化機制,在源對象和目標對象屬性名稱相同、屬性類型相同的情況下,使用CGLib實現的BeanCopier性能是最好的。

但是,當屬性名稱和屬性類型存在差異時,CGLib實現的BeanCopier會退化成暴力反射,因此性能會比較低下。而基於ASM實現的BeanCopier相對於CGLib則會更高效,但是其實現相對於CGLib複雜度會高些。

不過作為一個優秀的工具類,Beancopier並不是只能單獨使用。它也經常與其他框架、工具類一起使用(比如在Dubbo中的應用)。並且,Beancopier支持多線程操作,具有很好的線程安全性。

public class BeancopierPerformanceTest {
    private static final int COUNT = 100 * 10000;
    private static CglibBeanCopier cglibBeanCopier = CglibBeanCopier.create(Source.class, Target.class, true);
    private static AsmBeanCopier asmBeanCopier = AsmBeanCopier.create(Source.class, Target.class, true);
    private static BeanCopier beanCopier = BeanCopier.create(Source.class, Target.class, true);
    private static ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testPerformance() {
        Source sourceObject = getSourceObject();
        Stopwatch cglibWatch = Stopwatch.createStarted();
        for (int i = 0; i < COUNT; i++) {
            Target targetObject = new Target();
            cglibBeanCopier.copy(sourceObject, targetObject, null);
        }
        assertThat(cglibWatch.elapsed().toMillis()).isLessThan(1000);

        Stopwatch asmWatch = Stopwatch.createStarted();
        for (int i = 0; i < COUNT; i++) {
            Target targetObject = new Target();
            asmBeanCopier.copy(sourceObject, targetObject, null);
        }
        assertThat(asmWatch.elapsed().toMillis()).isLessThan(1000);

        Stopwatch watch = Stopwatch.createStarted();
        for (int i = 0; i < COUNT; i++) {
            Target targetObject = new Target();
            beanCopier.copy(sourceObject, targetObject, null);
        }
        assertThat(watch.elapsed().toMillis()).isLessThan(1000);
    }

    private Source getSourceObject() {
        Map sourceMap = Maps.newHashMap();
        sourceMap.put("id", 1);
        sourceMap.put("name", "John");
        sourceMap.put("age", 20);
        sourceMap.put("isDeleted", true);
        sourceMap.put("createTime", System.currentTimeMillis());
        return mapper.convertValue(sourceMap, Source.class);
    }

    static class Source {
        private int id;
        private String name;
        private int age;
        private boolean isDeleted;
        private long createTime;

        // getter and Setter
    }

    static class Target {
        private int id;
        private String name;
        private int age;
        private boolean isDeleted;
        private long createTime;

        // getter and Setter
    }
}

我們可以通過一個簡單的壓力測試來測試各種BeanCopier實現方式在拷貝大量數據時的性能表現。

結果顯示Beancopier的性能與其他實現方式相比不相上下,反應較快。

四、Beancopier是線程安全的嗎

Beancopier實現是線程安全的。由於其內部實現使用Map緩存了已經創建的BeanCopier對象,以達到高效的目的,但是在並發場景下,由於ConcurrentHashMap並不能完全規避並發產生的問題。因此,如果使用beancopier在多線程場景下頻繁獲取BeanCopier的實例,可能會存在一定的線程安全問題。

五、總結

Beancopier是一款非常值得推薦的Bean拷貝工具,其性能和使用上都很優秀。

如果需要實現Bean屬性間的快速拷貝,而且Bean屬性名稱和類型沒有出現差異,我們可以嘗試使用CGLib實現的BeanCopier,可以獲得最好的性能。

如果在Bean屬性名稱和類型存在差異時,我們可以嘗試使用Beancopier實現局部的類型轉換,不僅方便快捷,而且讓代碼更加簡潔明了。

原創文章,作者:HSEM,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/146153.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
HSEM的頭像HSEM
上一篇 2024-10-29 18:59
下一篇 2024-10-29 18:59

相關推薦

  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁盤中。在執行sync之前,所有的文件系統更新將不會立即寫入磁盤,而是先緩存在內存…

    編程 2025-04-25
  • 神經網絡代碼詳解

    神經網絡作為一種人工智能技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網絡的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網絡模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • Linux修改文件名命令詳解

    在Linux系統中,修改文件名是一個很常見的操作。Linux提供了多種方式來修改文件名,這篇文章將介紹Linux修改文件名的詳細操作。 一、mv命令 mv命令是Linux下的常用命…

    編程 2025-04-25
  • Python輸入輸出詳解

    一、文件讀寫 Python中文件的讀寫操作是必不可少的基本技能之一。讀寫文件分別使用open()函數中的’r’和’w’參數,讀取文件…

    編程 2025-04-25
  • nginx與apache應用開發詳解

    一、概述 nginx和apache都是常見的web服務器。nginx是一個高性能的反向代理web服務器,將負載均衡和緩存集成在了一起,可以動靜分離。apache是一個可擴展的web…

    編程 2025-04-25
  • MPU6050工作原理詳解

    一、什麼是MPU6050 MPU6050是一種六軸慣性傳感器,能夠同時測量加速度和角速度。它由三個傳感器組成:一個三軸加速度計和一個三軸陀螺儀。這個組合提供了非常精細的姿態解算,其…

    編程 2025-04-25
  • 詳解eclipse設置

    一、安裝與基礎設置 1、下載eclipse並進行安裝。 2、打開eclipse,選擇對應的工作空間路徑。 File -> Switch Workspace -> [選擇…

    編程 2025-04-25
  • C語言貪吃蛇詳解

    一、數據結構和算法 C語言貪吃蛇主要運用了以下數據結構和算法: 1. 鏈表 typedef struct body { int x; int y; struct body *nex…

    編程 2025-04-25
  • Python安裝OS庫詳解

    一、OS簡介 OS庫是Python標準庫的一部分,它提供了跨平台的操作系統功能,使得Python可以進行文件操作、進程管理、環境變量讀取等系統級操作。 OS庫中包含了大量的文件和目…

    編程 2025-04-25
  • Java BigDecimal 精度詳解

    一、基礎概念 Java BigDecimal 是一個用於高精度計算的類。普通的 double 或 float 類型只能精確表示有限的數字,而對於需要高精度計算的場景,BigDeci…

    編程 2025-04-25

發表回復

登錄後才能評論