Spring框架对BeanUtils.copyProperties的优化

前言

在高并发环境下,我们难免要进行大量的存库操作,而一般的操作是监听kafka然后将消息转换成实体类,再使用一些orm框架(mybatis-plus,jpa等)进行入库,我们在将消息转换的时候难免要用到反射,今天我们来讲讲Spring框架中对BeanUtils.copyProperties的优化

缓存机制

Spring 的 BeanUtils 类利用了缓存机制来加速属性的访问。具体来说,它缓存了属性描述符(PropertyDescriptor)以减少反射操作的次数。每次复制属性时,它会先检查缓存,以避免重复地使用反射来获取属性描述符

java 复制代码
private static final Map<Class<?>, PropertyDescriptor[]> propertyDescriptorCache =
        new ConcurrentHashMap<>(256);

更高效的反射操作

Spring 使用了一些优化的反射操作来提高性能,例如:

快速方法访问:使用 ReflectionUtils 提供的方法快速访问属性。

避免不必要的反射调用:通过缓存属性描述符和方法访问器,减少反射调用的开销

更灵活的类型转换

Spring 的 BeanUtils.copyProperties 支持类型转换,这意味着如果源对象和目标对象的属性类型不完全匹配,Spring 会尝试进行适当的类型转换。这是通过 Spring 的 ConversionService 实现的,它支持复杂的类型转换逻辑

更好的异常处理

Spring 的 BeanUtils 提供了更好的异常处理机制。它会捕获并处理各种可能的异常情况,并提供详细的异常信息,帮助开发者更快地发现和解决问题

支持嵌套属性复制

Spring 的 BeanUtils.copyProperties 支持嵌套属性的复制,这意味着它可以处理嵌套对象的属性复制,而不仅仅是简单属性。这使得它在处理复杂对象结构时更加灵活和方便。

支持忽略特定属性

Spring 的 BeanUtils.copyProperties 允许开发者指定在复制过程中忽略哪些属性。这对于需要部分属性复制的场景非常有用。

源码分析

以下是 Spring BeanUtils.copyProperties 方法的简化版源码,以展示其主要优化和特性:

java 复制代码
public static void copyProperties(Object source, Object target) throws BeansException {
    copyProperties(source, target, null, (String[]) null);
}

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;
    }
    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()))) {
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null &&
                        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);
                        }
                        writeMethod.invoke(target, value);
                    } catch (Throwable ex) {
                        throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                    }
                }
            }
        }
    }
}
相关推荐
七夜zippoe16 分钟前
缓存三大劫攻防战:穿透、击穿、雪崩的Java实战防御体系(三)
java·开发语言·缓存
lllsure20 分钟前
【Docker】镜像
java·spring cloud·docker
zhysunny23 分钟前
51.不可变基础设施:云原生时代的「乐高城堡」建造法
java·云原生
无名客031 分钟前
SQL语句执行时间太慢,有什么优化措施?以及衍生的相关问题
java·数据库·sql·sql语句优化
风槐啊35 分钟前
邪修实战系列(3)
java·ide·spring boot·spring·tomcat
咋吃都不胖lyh39 分钟前
SQL数据分析原代码--创建表与简单查询
java·数据库·sql
毕设源码-朱学姐41 分钟前
【开题答辩全过程】以 _基于SpringBoot技术的“树洞”心理咨询服务平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
boonya1 小时前
Java内存模型与线程私有共享区域与直接内存的理解
java·开发语言·内存模型
axban1 小时前
QT M/V架构开发实战:QAbstractItemModel介绍
java·数据库·qt
哈喽姥爷1 小时前
Spring Boot--yml配置信息书写和获取
java·数据库·spring boot·mybatis