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);
                    }
                }
            }
        }
    }
}
相关推荐
吴名氏.35 分钟前
Java转AI Agent教程(第十三周)混合检索与重排
java·数据库·人工智能·ai·ai agent
.痞老板.1 小时前
java ee--多线程(进阶)
java·java-ee
深入云栈3 小时前
Netty 4.2.x 源码深度解析 (二十三):ByteBuf 核心设计 —— 索引、类型与零拷贝
java·后端·架构
wang_shu_mo_ran4 小时前
MyBatis 从入门到进阶(一)
java·spring·mybatis
量化分析码农5 小时前
股票历史数据的复权参数对照:多级别返回的字段与数值差异
java·javascript·后端
猫猫不是喵喵.6 小时前
从0到1创建一个微服务项目
java·数据库·微服务·1024程序员节
瑞雪兆丰年兮6 小时前
JavaWeb-day13-SpringBootWeb事务管理&AOP
java·开发语言·springboot·javaweb·事务·web·aop
曲径海盗6 小时前
Try-Except怎么用,3个追问解答在自动化下的应用
java·jvm·oracle
Cosolar7 小时前
极简智库:快速搭建你的开源项目教程阅读平台,让 docs/ 目录被更多人看见
java·架构·github
nice_lcj5207 小时前
【从零理解 Spring AI · 16】Agent 与工作流:什么该写死,什么该让模型决定
java·人工智能·后端·spring ai·ai应用开发