Spring BeanUtils.copyProperties实现机制

Spring 框架中的 BeanUtils.copyProperties 方法提供了一种在两个 Java 对象之间复制属性的便捷方式。与 Apache Commons BeanUtils 类似,它也是基于反射来实现的。下面是关于其设计和实现的一些关键点:

设计思想

  1. 反射机制 :同样依赖 Java 的反射机制,SpringBeanUtils 使用反射来获取对象的属性值并将其设置到另一个对象中。
  2. 灵活性:提供了一些选项来增加灵活性,比如忽略某些属性或仅复制特定类型的属性。
  3. 性能优化:相比于 Apache Commons BeanUtils,Spring 的实现更注重性能优化。

实现步骤

  1. 参数验证:检查源对象和目标对象是否为 null,以避免空指针异常。

  2. 属性获取

    • 利用 Java 的 IntrospectorPropertyDescriptor 获取源对象和目标对象的所有属性。
    • 找到匹配的可读属性(getter 方法)和可写属性(setter 方法)。
  3. 属性匹配和复制

    • 遍历源对象的属性,并对每个属性执行以下操作:

      • 检查目标对象中是否存在同名且类型兼容的属性。
      • 如果存在,通过反射调用 getter 方法从源对象获取属性值,然后通过 setter 方法将该值设置到目标对象对应的属性上。
  4. 类型检查:确保只有当源和目标属性的类型兼容时才进行赋值,以防止类型不匹配的问题。

  5. 异常处理 :捕获反射过程中可能发生的异常,如 IllegalAccessExceptionInvocationTargetException 等,并适当地处理这些异常。

注意事项

  • 浅拷贝BeanUtils.copyProperties 进行的是浅拷贝。如果属性是引用类型,复制的是引用而不是实际对象。
  • 忽略属性:方法允许指定要忽略的属性数组,从而不会复制这些属性。
  • 自定义转换器 :Spring 的 BeanUtils 支持注册自定义属性编辑器,以支持更复杂的数据类型转换。
  • 性能考虑:由于会涉及反射,虽然经过优化,但在大量对象复制或频繁调用时仍需注意性能。
  • 嵌套属性:对于嵌套对象的属性,不会自动递归复制,需要手动执行属性复制操作。

核心代码示例

以下是简化后的核心代码逻辑示例:

java 复制代码
public static void copyProperties(Object source, Object target) throws BeansException {
    // 参数验证
    if (source == null || target == null) {
        throw new IllegalArgumentException("Source and target must not be null");
    }

    // 获取源对象和目标对象的属性描述符
    PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(target.getClass());
    for (PropertyDescriptor targetPd : targetPds) {
        if (targetPd.getWriteMethod() != null) { // 目标属性必须有setter
            PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null && sourcePd.getReadMethod() != null) { // 源属性必须有getter
                try {
                    // 调用源对象的getter方法
                    Method readMethod = sourcePd.getReadMethod();
                    makeAccessible(readMethod);
                    Object value = readMethod.invoke(source);

                    // 调用目标对象的setter方法
                    Method writeMethod = targetPd.getWriteMethod();
                    makeAccessible(writeMethod);
                    writeMethod.invoke(target, value);
                } catch (Throwable ex) {
                    throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                }
            }
        }
    }
}

private static void makeAccessible(Method method) {
    if (!Modifier.isPublic(method.getDeclaringClass().getModifiers()) || !Modifier.isPublic(method.getModifiers())) {
        method.setAccessible(true);
    }
}

关键点分析

  • 反射机制 :通过 Method.invoke() 调用 getter 和 setter,这是反射的典型应用。
  • 访问权限makeAccessible 方法确保即便属性或类不是公共的,也可以通过反射访问。
  • 异常处理 :在反射操作中,一旦出现问题,比如访问权限、方法不存在等,会抛出异常,这里包装成 FatalBeanException 并重新抛出。

Apache Commons BeanUtils对比

Spring 的 BeanUtils.copyProperties 相比于 Apache Commons BeanUtils,确实在性能上做了一些优化。以下是分析两者之间的主要区别和优化点:

1. 属性获取方式

  • Apache Commons BeanUtils

    • 使用 PropertyUtils 来获取属性描述符,这一过程涉及较多的反射调用。
    • 其实现通常需要访问 DynaBeanConverter,增加了复杂性和额外的开销。
  • Spring BeanUtils

    • 使用 Java 内置的 IntrospectorPropertyDescriptor 来获取属性信息。这种方法相对轻量级,因为它不需要像 Apache Commons BeanUtils 那样通过 DynaBeans 等更复杂结构进行处理
    • 简化访问控制 :Spring 在执行反射操作时,通过 ReflectionUtils.makeAccessible() 快速处理非公共方法的访问权限问题,而 Apache Commons BeanUtils 有时可能不得不采用更多的路径来处理类似问题

2. 缓存机制

  • Apache Commons BeanUtils

    • 缓存机制相对简单,主要依赖于 JVM 自身的类缓存。
    • 一些转换操作没有充分利用缓存。
  • Spring BeanUtils

    • 类元数据缓存 :Spring 对类的 PropertyDescriptor 结果进行缓存,这意味着首次获取后,后续操作可以复用缓存的数据,减少了重复解析带来的开销。这个缓存通常实现为静态或线程安全的结构,以最大化复用效率。

3. 类型检查和转换

  • Apache Commons BeanUtils

    • 使用通用的 Converter 进行类型转换,灵活性高但性能略逊。
    • 检查和转换都比较泛化,可能导致一些不必要的性能损耗。
  • Spring BeanUtils

    • 提前验证:Spring 在复制前会确保源和目标属性之间的类型兼容性,避免不必要的反射失败尝试。这在很大程度上优化了性能,因为未通过检查的属性会被立即跳过,而不会尝试进行反射调用。
    • ConversionService 支持 :Spring 提供灵活的 ConversionService 来支持属性值的自动类型转换,这意味着开发者可以通过配置减少手动转换的负担,从而提高整体性能

4. 异常处理

  • Apache Commons BeanUtils

    • 异常处理较为频繁,因为要兼容更多的场景和自定义行为。
    • 更多地使用受检异常,增加了性能开销。
  • Spring BeanUtils

    • 统一的异常策略 :Spring 通过抛出 RuntimeException(如 FatalBeanException)来统一反射过程中遇到的各种异常。这种方式简化了代码逻辑,同时也提升了异常处理的性能,因为省去了捕获每个特定异常的开销。

5. 设计目标

  • Apache Commons BeanUtils

    • 设计上更加通用,为了适应更多的应用场景而牺牲了一定的性能。
    • 由于需要支持 DynaBean 和其他动态特性,增加了复杂度。
  • Spring BeanUtils

    • 目标明确,专注于高效、简单的 JavaBean 属性复制。
    • 更倾向于内省和反射技术的优化。

总的来说,Spring 的 BeanUtils.copyProperties 在性能优化上做了很多针对性的改进,如缓存机制、简化的反射操作、以及异常处理方面的提升。它的设计更贴近于实际的 JavaBean 操作,而 Apache Commons BeanUtils 则提供了更多样化的功能,适合需要更广泛支持的场景。

扩展:DynaBean分析

DynaBean 是 Apache Commons BeanUtils 库中的一个接口,用于创建动态 JavaBean。与传统 JavaBean 不同,DynaBean 不需要在编译时定义所有属性,它可以在运行时动态地添加和访问属性。这种机制为需要灵活处理不确定数据结构的场景提供了便利。

主要特点

  1. 动态属性管理

    • 可以在运行时添加、修改或删除属性,而不需要预先定义类中的字段。
  2. 灵活的数据结构

    • 提供了一种类似于映射(Map)或字典的方式来存储属性及其值,可以非常方便地操作不规则的数据。
  3. 接口方法

    • get(String name): 获取属性值。
    • set(String name, Object value): 设置属性值。
    • contains(String name): 检查是否存在某个属性。
    • remove(String name): 删除属性。

使用场景

  • 动态数据处理 :在处理结构不固定的数据(如解析 XML/JSON 数据)时,可以使用 DynaBean 来简化代码。
  • 快速原型开发 :当需要快速迭代和修改对象结构时,DynaBean 提供了极大的灵活性。
  • 表单和数据输入 :在处理用户输入或表单数据时,可能会用到 DynaBean 来适配各种不同的输入格式。

总之,DynaBean 提供了一种灵活的方式来处理动态数据需求,在需要弹性数据结构的应用程序中非常有用

相关推荐
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫19 分钟前
泛型(2)
java
南宫生28 分钟前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石36 分钟前
12/21java基础
java
李小白6644 分钟前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp1 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
n北斗2 小时前
常用类晨考day15
java
骇客野人2 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
AskHarries2 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端