千锋教育-做有情怀、有良心、有品质的职业教育机构

手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

当前位置:首页  >  技术干货  > Beancopier全方位详解

Beancopier全方位详解

来源:千锋教育
发布人:xqq
时间: 2023-11-24 11:38:07 1700797087

一、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实现局部的类型转换,不仅方便快捷,而且让代码更加简洁明了。

tags: beancopier
声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。
10年以上业内强师集结,手把手带你蜕变精英
请您保持通讯畅通,专属学习老师24小时内将与您1V1沟通
免费领取
今日已有369人领取成功
刘同学 138****2860 刚刚成功领取
王同学 131****2015 刚刚成功领取
张同学 133****4652 刚刚成功领取
李同学 135****8607 刚刚成功领取
杨同学 132****5667 刚刚成功领取
岳同学 134****6652 刚刚成功领取
梁同学 157****2950 刚刚成功领取
刘同学 189****1015 刚刚成功领取
张同学 155****4678 刚刚成功领取
邹同学 139****2907 刚刚成功领取
董同学 138****2867 刚刚成功领取
周同学 136****3602 刚刚成功领取
相关推荐HOT