JavaBean对象属性copy

本文对比了多种对象属性拷贝方法的性能,包括HardCode、BeanCopier及BeanUtils等,并介绍了BeanCopier的工作原理及如何处理不同字段名的情况。

1.对象属性拷贝的常见方式及其性能

  在日常编码中,经常会遇到DO、DTO对象之间的转换,如果对象本身的属性比较少的时候,那么采用Hard Code工作量也不大,但如果对象的属性比较多的情况下,Hard Code效率就比较低。这时候就要使用其它工具类来进行对象属性的拷贝。
  常用的对象属性拷贝的方式和性能测试如下:

拷贝方式copy次数1000copy次数100000copy次数1000000
Hard Code1 ms18 ms43 ms
net.sf.cglib.beans.BeanCopier#copy117 ms107 ms110 ms
org.springframework.beans.BeanUtils#copyProperties137 ms246 ms895 ms
org.apache.commons.beanutils.PropertyUtils#copyProperties212 ms601 ms7869 ms
org.apache.commons.beanutils.BeanUtils#copyProperties275 ms1732 ms12380 ms

  结论:采用Hard Code方式进行对象属性Copy性能最佳;采用net.sf.cglib.beans.BeanCopier#copy方式进行对象属性copy性能最稳定;而org.apache.commons.beanutils.BeanUtils.copyProperties 方式在数据量大时性能下降最厉害。所以在日常编程中遇到具有较多属性的对象进行属性复制时优先考虑采用net.sf.cglib.beans.BeanCopier#copy

  以上的数据之所以产生巨大差距的原因在于其实现原理与方式的不同而导致的,Hard Code直接调用getter & setter方法值,cglib采用的是字节码技术,而后三种均采用反射的方式。前两者性能优异众所周知,但为何同样采用反射的方式进行属性Copy时产生的差异如此巨大呢?

  

2.Introspector

  org.apache.commons.beanutils.BeanUtilsorg.springframework.beans.BeanUtils均采用反射技术实现,也都调用了Java关于反射的高级API——Introspector(内省)。Introspector(内省)是jdk提供的用于描述Java bean支持的属性、方法以及事件的工具;利用此类可得到BeanInfo接口的实现对象:

        BeanInfo beanInfo = Introspector.getBeanInfo(Client.class);

        BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
        System.out.println(beanDescriptor.getBeanClass());

        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor descriptor: descriptors) {
            Method readMethod = descriptor.getReadMethod();
            Method writeMethod = descriptor.getWriteMethod();

            System.out.println(descriptor.getName()); // address
            System.out.println(readMethod); // public java.lang.String xxx.Client.getAddress()
            System.out.println(writeMethod); // public void xxx.Client.setAddress(java.lang.String)
        }

  getBeanDescriptor()返回的BeanDescriptor提供了java bean的一些全局的信息,如class类型、类名称等。getPropertyDescriptors()返回PropertyDescriptor[],PropertyDescriptor描述了java bean中一个属性和它们的getter & setter方法的SoftReference。

  

3.BeanCopier#copy的使用

        Client client = new Client();
        client.setAddress("adc");
        client.setEmail("@163.com");
        BeanCopier copier = BeanCopier.create(Client.class, ClientDO.class, false);
        ClientDO clientDO = new ClientDO();
        copier.copy(client, clientDO, null);

  BeanCopier#copy的使用示例有很多,注意BeanCopier只拷贝名称和类型都相同的属性,使用Converter可以解决类型不同的字段拷贝。
  那么,如果字段名称不一样,但是需要BeanCopier#copy来拷贝怎么弄呢?这里给一个解法,可能不是最优解。我们知道BeanCopier#copy是使用了asm来生成了一个转换类来做属性的拷贝,只要修改生成的类里面的逻辑即可,来看代码:

    public static BeanCopier create(Class source, Class target, boolean useConverter) {
        Generator gen = new Generator();
        gen.setSource(source);
        gen.setTarget(target);
        gen.setUseConverter(useConverter);
        return gen.create();
    }

  默认情况下,使用了net.sf.cglib.beans.BeanCopier.Generator,注意它是ClassGenerator的实现类,那么在生成类的使用就会调用它的generateClass方法:

        public void generateClass(ClassVisitor v) {
            Type sourceType = Type.getType(source);
            Type targetType = Type.getType(target);
            ClassEmitter ce = new ClassEmitter(v);
            ce.begin_class(Constants.V1_2,
                           Constants.ACC_PUBLIC,
                           getClassName(),
                           BEAN_COPIER,
                           null,
                           Constants.SOURCE_FILE);

            EmitUtils.null_constructor(ce);
            CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, COPY, null);
            PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
            PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);

            Map names = new HashMap();
            for (int i = 0; i < getters.length; i++) {
                names.put(getters[i].getName(), getters[i]);
            }
            Local targetLocal = e.make_local();
            Local sourceLocal = e.make_local();
            if (useConverter) {
                e.load_arg(1);
                e.checkcast(targetType);
                e.store_local(targetLocal);
                e.load_arg(0);                
                e.checkcast(sourceType);
                e.store_local(sourceLocal);
            } else {
                e.load_arg(1);
                e.checkcast(targetType);
                e.load_arg(0);
                e.checkcast(sourceType);
            }
            for (int i = 0; i < setters.length; i++) {
                PropertyDescriptor setter = setters[i];
                PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
                if (getter != null) {
                    MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
                    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
                    if (useConverter) {
                        Type setterType = write.getSignature().getArgumentTypes()[0];
                        e.load_local(targetLocal);
                        e.load_arg(2);
                        e.load_local(sourceLocal);
                        e.invoke(read);
                        e.box(read.getSignature().getReturnType());
                        EmitUtils.load_class(e, setterType);
                        e.push(write.getSignature().getName());
                        e.invoke_interface(CONVERTER, CONVERT);
                        e.unbox_or_zero(setterType);
                        e.invoke(write);
                    } else if (compatible(getter, setter)) {
                        e.dup2();
                        e.invoke(read);
                        e.invoke(write);
                    }
                }
            }
            e.return_value();
            e.end_method();
            ce.end_class();
        }

  asm不熟悉也不要紧,关键在于

                PropertyDescriptor setter = setters[i];
                PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
                if (getter != null) {
                    MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
                    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());

  根据target中的setter的name,即字段名,去source中取getter,取不到就拉倒。那比如target有个字段是gender,但是实际对应source字段应该是sex,实际上可以在gender这个字段上加一个注解,值是sex,用注解值去source中取getter,然后做后面流程:

            PropertyDescriptor getter = (PropertyDescriptor) names.get(setter.getName());
            if (getter == null) {
                try {
                    Field field = target.getDeclaredField(setter.getName());
                    Alias alias = field.getAnnotation(Alias.class);
                    if (alias != null) {
                        getter = (PropertyDescriptor) names.get(alias.value());
                    }
                } catch (NoSuchFieldException err) {
                    err.printStackTrace();
                }
            }

  为了避免对源码的修改,可以自定义一个ConsumerGenerator extends BeanCopier.Generator,在创建BeanCopier时使用ConsumerGenerator生成。

  
参考:https://blog.youkuaiyun.com/u010209217/article/details/84837821#1__2

### JavaBean 使用指南及常见问题解决方案 JavaBean 是一种特殊的 Java 类型,用于封装数据和行为。它们通常被设计为可重用组件,并广泛应用于各种企业级开发场景中。为了帮助开发者更好地理解和使用 JavaBean,下面将详细介绍其基本概念、最佳实践以及一些常见的问题解决策略。 #### 什么是 JavaBeanJavaBean 是遵循特定约定的 Java 类,具有以下几个特点: - 提供无参构造函数以便实例化。 - 私有成员变量并通过 getter 和 setter 方法访问。 - 实现 `java.io.Serializable` 接口以支持序列化功能[^3]。 这样的设计使得 JavaBeans 成为一种标准化的方式存储信息并与外部世界交互。 #### 创建简单的 JavaBean 示例 以下是一个典型的 JavaBean 定义方式的例子: ```java public class User implements java.io.Serializable { private String name; private int age; // No-argument constructor required by specification. public User() {} // Getter and Setter methods for properties 'name' and 'age'. public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 这段代码展示了如何构建一个符合标准规范的基础类——即所谓的 **JavaBean**[^3]。 #### 集成 Apache Commons BeanUtils 处理复杂场景 当面对更复杂的业务逻辑或者需要批量操作多个 Beans 时,可以借助第三方库如 *Apache Commons BeanUtils* 来简化流程。不过需要注意的是,在引入此类工具前要仔细挑选适合项目的版本号以防出现兼容性冲突等问题[^1]。 例如,我们可以轻松复制两个相似但又有所区别的实体之间的属性值: ```java import org.apache.commons.beanutils.BeanUtils; try{ Destination destObject=new Destination(); Source sourceObject=new Source(); // Copy all property values from one object to another. BeanUtils.copyProperties(destObject,sourceObject); }catch(Exception e){ logger.error("Error during beans copy.",e); } ``` 这里我们利用了 `copyProperties()` 方法实现了跨对象的数据迁移,极大地减少了手动编码的工作量的同时也提高了程序健壮性[^1]。 #### 数据校验的重要性 - Hibernate Validator 应用案例 除了简单地传递参数外,很多时候还需要验证传入的数据是否满足预期的要求。这时就可以采用像 Hibernate Validator 这样的框架来增强系统的鲁棒性。它是 JSR 380 (Bean Validation 2.0) 的主要参考实现之一,提供了丰富的内置约束注解集合作用来标记领域模型中的各个字段[^2]。 比如我们在前面提到过的 `User` 类基础上增加了一些额外限制条件: ```java import javax.validation.constraints.*; public class ValidatedUser { @Size(min=2,max=30,message="Name must be between 2 and 30 characters long.") private String name; @Min(value=18,message="Must be at least 18 years old") @Max(value=99,message="Cannot exceed maximum allowed age limit of 99") private Integer age; // Standard getters & setters omitted here... } ``` 现在每当创建新的用户记录之前都会自动触发相应的规则检查从而避免非法输入造成后续麻烦。 #### JPA 中管理持久化的 JavaBeans 在现代 Web 应用开发过程中不可避免会涉及到数据库层面的操作。此时 Spring Boot Data JPA 可以为我们的项目带来极大便利因为它已经预先打包好了 Hibernate Core 所需的一切必要依赖项[^4]。 只需添加如下 Maven POM 文件片段就能启用完整的 ORM 功能支持: ```xml <dependencies> <!-- Other dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies> ``` 之后便可以在 Repository 层定义查询接口无需关心底层 SQL 细节让整个架构变得更加清晰明了[^4]。 #### Orika 映射框架简介 最后值得一提还有 Orika —— 一款专注于高性能的对象映射解决方案特别适配那些存在大量 DTO 转换需求的企业系统。相比起传统的反射机制它采用了动态代理技术和缓存策略大大提升了效率同时还保留足够的灵活性适应不同的实际应用场景[^5]。 假设我们需要把某种 VO (Value Object)转化为对应的 POJO 形式那么只需要几行简洁优雅的语句就搞定了! ```java MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); ClassMapBuilder<UserVO, UserModel> userMapping = mapperFactory.classMap(UserVO.class, UserModel.class); // Customize mapping rules if necessary... userMapping.byDefault(); // Enable default field mappings based on names matchings. mapperFactory.register(userMapping); // Perform actual conversion later somewhere inside service layer code path. UserModel convertedInstance=mapper.map(originalVoInstance, UserModel.class); ``` 通过这种方式不仅可以让原本繁琐的手动赋值得益于高度抽象变得异常简便而且还能进一步降低潜在错误发生的概率提升整体质量水平[^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值