为什么说要慎用BeanUtils,因为性能真的拉跨

news2024/9/21 12:38:58

1 背景

之前在专栏中讲过“不推荐使用属性拷贝工具”,推荐直接定义转换类和方法使用 IDEA 插件自动填充 get / set 函数。

不推荐的主要理由是:

  • 有些属性拷贝工具性能有点差

  • 有些属性拷贝工具有“BUG”

  • 使用属性拷贝工具容易存在一些隐患(后面例子会讲到)

2 示例

2.1 Spring 的 BeanUtils 的属性拷贝

首先公司内部就遇到过 commons 包的 BeanUtils 进行属性拷贝性能较差的真实案例,然后该同事换成了 Spring 的 BeanUtils 性能好了很多,感兴趣大家可以使用性能测试框架或者基准测试框架去对比,这里就不对比了。

接下来我们看 Spring 的 BeanUtils 的属性拷贝会存在啥问题:

import lombok.Data;

import java.util.List;

@Data
public class A {
    private String name;

    private List<Integer> ids;
}
@Data
public class B {
    private String name;

    private List<String> ids;
}
import org.springframework.beans.BeanUtils;

import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = new B();
        BeanUtils.copyProperties(first, second);
        for (String each : second.getIds()) {// 类型转换异常
            System.out.println(each);
        }
    }
}

大家运行上述示例时,会发生类型转换异常。

打断点可以看到,属性拷贝之后 B 类型的 second 对象中 ids 仍然为 Integer 类型:

如果不转换为字符串,直接进行打印,并不会报错。

2.2 CGlib 在不定义Converter 的情况

使用CGlib 在不定义Converter 的情况下也会遇到类似问题:

import org.easymock.cglib.beans.BeanCopier;

import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = new B();
        final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false);
        beanCopier.copy(first,second,null);

        for (String each : second.getIds()) {// 类型转换异常
            System.out.println(each);
        }
    }
}

同样,问题在运行时才暴露出来。

2.3 mapstruct

接下来我们看下 mapstruct:

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface Converter {
    Converter INSTANCE = Mappers.getMapper(Converter.class);

    B aToB(A car);
}
import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = Converter.INSTANCE.aToB(first);
        for (String each : second.getIds()) {// 正常
            System.out.println(each);
        }
    }
}

可以成功的将 A 中 List<Integer> 转为 B 中的 List<String> 类型。

我们看下编译生成的 Converter 实现类:

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class ConverterImpl implements Converter {

    @Override
    public B aToB(A car) {
        if ( car == null ) {
            return null;
        }

        B b = new B();

        b.setName( car.getName() );
        b.setIds( integerListToStringList( car.getIds() ) );

        return b;
    }

    protected List<String> integerListToStringList(List<Integer> list) {
        if ( list == null ) {
            return null;
        }

        List<String> list1 = new ArrayList<String>( list.size() );
        for ( Integer integer : list ) {
            list1.add( String.valueOf( integer ) );
        }

        return list1;
    }
}

自动帮我们进行了转换,我们可能没有意识到类型并不一致。

如果我们在 A 类中添加一个 String number 属性,在 B 类中添加一个 Long number 属性,使用 mapstruect 当 number 设置为非数字类型时就会报 .NumberFormatException 。

    @Override
    public B aToB(A car) {
        if ( car == null ) {
            return null;
        }

        B b = new B();

        b.setName( car.getName() );
        if ( car.getNumber() != null ) { // 问题出在这里
            b.setNumber( Long.parseLong( car.getNumber() ) );
        }
        b.setIds( integerListToStringList( car.getIds() ) );

        return b;
    }

使用 cglib 默认则不会映射 number 属性,B 中的 number 为 null。

2.4 手动定义转换器

如果手动定义转换器,使用 IDEA 插件(如 generateO2O)自动转换:

public final class A2BConverter {

    public static B from(A first) {
        B b = new B();
        b.setName(first.getName());
        b.setIds(first.getIds());
        return b;
    }
}

在编码阶段就可以非常明确地发现这个问题:

3 结论

由于 Java 的泛型其实是编译期检查,编译后泛型擦除,导致运行时 List<Integer> 和 List<String> 都是 List 类型,可以正常赋值。这就导致在使用很多属性映射工具时,编译时不容易明显的错误。

mapstruct 自定义了注解处理器,在编译阶段可以读取映射双方的泛型类型,进而进行映射。但是这种映射也很可怕,有时候我们由于粗心等原因定义错了类型,自动帮助我们进行了转换,会带了很多副作用。

之前对各种属性映射工具的性能进行了简单的对比,结果如下:

因此慎用属性转换工具,如果可能建议自定义转换类,使用 IDEA插件自动填充,效率也挺高, A 或 B 中任何属性类型不匹配,甚至删除一个属性,编译阶段即可报错,而且直接调用 get set 的效率也是非常高的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

九龙证券|磷酸亚铁锂是什么?磷酸亚铁锂的特点和性能介绍

磷酸亚铁锂是一种新式锂离子电池电极资料&#xff0c;化学式&#xff1a;LiFePO4&#xff0c;磷酸亚铁锂为近来新开发的锂离子电池电极资料&#xff0c;首要用于动力锂离子电池&#xff0c;作为正极活性物质运用&#xff0c;人们习气也称其为磷酸铁锂。 磷酸亚铁锂的特色和功能…

【计算机网络】随机访问介质访问控制中的ALOHA协议、CSMA协议、CSMA/CD协议和CSMA/CA协议

二、随机访问介质访问控制 信道上的所有用户可随机发送信息 1. 简单ALOHA协议 解决信息发送时的冲突问题。其特点是不监听信道&#xff0c;不按时间槽发送&#xff0c;随机重发。 思想&#xff1a;在超时后&#xff0c;等随机一段时间后再重传 2. 时隙ALOHA协议 时隙ALOH…

送给她最最浪漫的表白(Python代码实现)

目录I 最美的浪漫 II 昨日浪漫的表白III 关于“美”的哲思 IV Python代码实现 I 最美的浪漫 II 昨日浪漫的表白 她问:有一个问题&#xff0c;我只问一遍&#xff0c;为什么是我?我微微一笑答道:笞案很长&#xff0c;我要用一生来回答。我&#xff1a;图书馆旁边的星空真美&…

Linux面试总结

一.常用命令1.目录切换cd / 切换到根目录cd ../ 切换到上级目录cd ~ 切换到home目录2.查看目录ls 列出当前目录下所有的文件ls [路径]ls / 查看根目录 ls -l 相当于 ll 最常用的命令,用了表的方式列出当前目录的内容3.查看当前目录pwd-4.创建一组空文件touch5.显示文件内容cat6…

Maven打包操作

对于企业级项目&#xff0c;无论是进行本地测试&#xff0c;还是测试环境测试以及最终的项目上线&#xff0c;都会涉及项目的打包操作。对于每个环境下的项目打包&#xff0c;对应的项目所需要的配置资源都会有所区别&#xff0c;实现打包的方式有很多种&#xff0c;可以通过an…

【Linux】进程理解与学习(Ⅰ)

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;相关文章推荐&#xff1a;【Linux】冯.诺依曼体系结构与操作系统进程概念什么是进程&#xff1f;进程是什么&#xff1f;我们打开任务管理器可以看到有…

Flex弹性布局一文通【最全Flex教学】

文章目录一.Flex布局1.1 传统布局和flex布局1.1.1 传统布局1.1.2 flex弹性布局1.2 flex初步体验1.3 布局原理二.常见Flex属性2.1 常见父项属性2.2 flex-direction主轴的方向2.3 justify-content设置主轴上的子元素排列方式2.4 设置子元素是否flex-wrap换行2.5 align-itmes设置侧…

数列的极限

数列的极限的定义&#xff1a; 当正多边形的边数越来越多时&#xff0c;正多边形的面积越来越接近于圆的面积。 这个过程就叫做极限。 数列的形式&#xff1a; 数列的每一个下标对应一个数列元素值。 认识数列 这就是4个数列的表现形式。 当数列的下标无限增长时&#xff0c…

记录--uni-app中安卓包检查更新、新版本下载、下载进度条显示功能实现

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 需求描述 如果想要做一个app的话&#xff0c;可以有很多种选择方案&#xff0c;uni-app是其中的一个性价比高一些(坑多一些)的方案。本文记录一下&#xff0c;uni-app打安卓包以后&#xff0c;需要检查…

【操作系统】如何避免预读失效和缓存污染的问题?

【操作系统】如何避免预读失效和缓存污染的问题&#xff1f; 文章目录【操作系统】如何避免预读失效和缓存污染的问题&#xff1f;Linux 和 MySQL 的缓存Linux 操作系统的缓存MySQL 的缓存传统 LRU 是如何管理内存数据的&#xff1f;预读失效&#xff0c;怎么办&#xff1f;什么…

bash if 判断及特殊字符

bash if 判断 1)字符串判断 str1 str2     当两个串有相同内容、长度时为真 str1 ! str2     当串str1和str2不等时为真 -n str1       当串的长度大于0时为真(串非空) -z str1       当串的长度为0时为真(空串) str1        当串str1为非…

华为OD机试题,用 Java 解【众数和中位数】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

Spark DPP

Spark DPP分区剪裁动态分区剪裁DPP (Dynamic Partition Pruning&#xff0c;动态分区剪裁) : 过滤维度表后&#xff0c;能削减事实表的数据扫描量&#xff0c;提升关联计算的执行性能 分区剪裁 需求 &#xff1a;统计所有头部用户贡献的营业额&#xff0c;并按照营业额倒序排…

体验 Linux 音频驱动

目录 一、音频接口简介 1、音频编解码芯片 2、 WM8960 3、I2S总线接口 二、硬件原理图 三、音频驱动使能 三、使能内核的 WM8960 驱动 1、取消 ALSA 模拟 OSS API 2、使能 I.MX6ULL 的 WM8960 驱动 验证 四、alsa-lib 和 alsa-utils 移植 1、alsa-utils 移植 ①、开…

AUTOSAR知识点Com(四):CANIf接收

1、概述 根据AUTOSAR BSW架构&#xff0c;接收到的数据将在上层通信模块&#xff0c;即AUTOSAR COM、CanNm、CanTp和DCM中进行评估和处理。这意味着&#xff0c;上层模块既不能使用CanDrv的缓冲区&#xff0c;也不能访问CanIf的缓冲区。只有当CanIfPublicReadRxPduDataApi设置为…

自考本科计算机网络原理(04741)历年大题真题【18年10月-22年10月】

文章目录一、简答题&#xff08;历年真题&#xff09;18年10月-22年10月历年简答题出题情况分析2018年10月2019年4月2019年10月2020年8月2020年10月2021年4月2021年10月2022年4月2022年10月二、综合题&#xff08;历年真题&#xff09;2018年10月2019年4月2019年10月2020年8月2…

javaFunction函数,函数式接口,HashMap中的computeIfAbsent() 方法的使用

由于平时很少用到这种写法&#xff0c;导致每次看到有一脸懵逼&#xff0c;然后一段时间就要总结复习一次。。。。。。如图所示???这是什么牛马写法? Function<String, String> toUpperCase str -> str.toUpperCase();仔细分析一下&#xff0c;这因该要新建一个对…

Allegro如何使用自带的功能更改差分的线宽和间距操作指导

Allegro如何使用自带的功能更改差分的线宽和间距操作指导 在做PCB设计的时候,有时候PCB已经完成了布线,但是因为某些原因更改了层叠导致了差分的线宽和间距发生了变化,导致差分需要重新走一遍,如下图 如果差分数量比较多,重走是比较费时间的,Allegro自身有快速更新差分线…

服务器源码安装openssh7.4p1

系统环境&#xff1a; OS&#xff1a;Kylin-Server-10-SP2-x86-Release-Build09-20210524 软件版本&#xff1a;OpenSSH_8.2p1, OpenSSL 1.1.1f 一、源码安装升级 1、下载源码包 下载zlib-1.2.11.tar.gz、openssl-1.0.2k.tar.gz、openssh-7.4p1.tar.gz&#xff0c; openssl不…

【强化学习】一文弄懂,Q-learning和Sarsa的区别

一文弄懂&#xff0c;Q-learning和Sarsa的区别 2023.3.6 本文主要参考莫烦老师内容1. 理解Q_learning算法 主要看下面讲解&#xff0c;就不抄了&#xff1a; https://mofanpy.com/tutorials/machine-learning/reinforcement-learning/intro-q-learning 读懂几个内容&#xf…