为什么不推荐使用BeanUtils属性转换工具示例详解

什么是BeanUtils工具

BeanUtils工具是一种方便我们对JavaBean进行操作的工具,是Apache组织下的产品。

BeanUtils工具一般可以方便javaBean的哪些操作?

1)beanUtils 可以便于对javaBean的属性进行赋值。

2)beanUtils 可以便于对javaBean的对象进行赋值。

3)beanUtils可以将一个MAP集合的数据拷贝到一个javabean对象中。

1 背景

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

不推荐的主要理由是:

  • 有些属性拷贝工具性能有点差
  • 有些属性拷贝工具有“BUG”
  • 使用属性拷贝工具容易存在一些隐患(后面例子会讲到)

2 示例

首先公司内部就遇到过 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 类型:

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

使用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);
 }
 }
}

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

接下来我们看下 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。

如果手动定义转换器,使用 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 的效率也是非常高的。

到此这篇关于为什么不推荐使用BeanUtils属性转换工具示例详解的文章就介绍到这了,更多相关BeanUtils属性转换工具内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何使用BeanUtils.copyProperties进行对象之间的属性赋值

    1.使用org.springframework.beans.BeanUtils.copyProperties方法进行对象之间属性的赋值,避免通过get.set方法一个一个属性的赋值 /** * 对象属性拷贝 <br> * 将源对象的属性拷贝到目标对象 * * @param source 源对象 * @param target 目标对象 */ public static void copyProperties(Object source, Object target) { try { BeanU

  • Commons beanutils组件简介

    Commons Beanutils是Apache开源组织提供的用于操作JAVA BEAN的工具包.使用commons beanutils,我们可以很方便的对bean对象的属性进行操作.今天为大家介绍一下该包的常用方法. 1.什么是BeanUtils 程序中对javabean的操作很频繁, 所以apache提供了一套开源的api,方便对javabean的操作,即BeanUtils组件. 2.BeanUtils的作用 简化javabean的操作. 在一般的写bean组件的时候,都必须要写setter

  • JSP 开发之Spring BeanUtils组件使用

    JSP 开发之Spring BeanUtils组件使用 用于演示的javabean import java.util.Date; public class People { private String name; private int age; private Date birth; public People(String name, int age, Date birth) { super(); this.name = name; this.age = age; this.birth =

  • 为什么不推荐使用BeanUtils属性转换工具示例详解

    什么是BeanUtils工具 BeanUtils工具是一种方便我们对JavaBean进行操作的工具,是Apache组织下的产品. BeanUtils工具一般可以方便javaBean的哪些操作? 1)beanUtils 可以便于对javaBean的属性进行赋值. 2)beanUtils 可以便于对javaBean的对象进行赋值. 3)beanUtils可以将一个MAP集合的数据拷贝到一个javabean对象中. 1 背景 之前在专栏中讲过"不推荐使用属性拷贝工具",推荐直接定义转换类和方

  • 封装flutter状态管理工具示例详解

    目录 引言 RxBinder 代码实现 Demo 完美运行 引言 关于 Flutter 状态管理,公司项目使用的是Bloc方案.Bloc 其实本质上是 provider 的封装扩展库,整体通过 InheritedWidget .Notifier 外加 Stream中转实现状态变更通知. 关于 Bloc 实现原理,有兴趣的同学可以观看这篇文章 Bloc原理解析 RxBinder 撇开Bloc内部实现策略,小轰尝试基于数据驱动模型,自定义一套状态管理工具.构思如下: 主要成员如下: RxBinder

  • Unity AssetBundle打包工具示例详解

    目录 Unity批量打AB包 1.PathTool 2.CreateAB 3.ClearABLable 4.拓展 Unity批量打AB包 为了资源热更新,Unity支持将所有资源打包成AssetBundle资源,存放在SteamingAssets文件夹中: 在项目发布之前,需要将所有资源打包成.ab文件,动态加载: 在项目更新时,替换.ab资源文件,即可完成热更新: ab文件在加载时,会多一步解压缩的过程,会增加性能消耗: 打包操作属于编辑器拓展,所有脚本放在Eidtor文件夹下: 1.Path

  • 关于Python可视化Dash工具之plotly基本图形示例详解

    Plotly Express是对 Plotly.py 的高级封装,内置了大量实用.现代的绘图模板,用户只需调用简单的API函数,即可快速生成漂亮的互动图表,可满足90%以上的应用场景. 本文借助Plotly Express提供的几个样例库进行散点图.折线图.饼图.柱状图.气泡图.桑基图.玫瑰环图.堆积图.二维面积图.甘特图等基本图形的实现. 代码示例 import plotly.express as px df = px.data.iris() #Index(['sepal_length', '

  • Awaitility同步异步工具实战示例详解

    目录 引言 1. awaitility入门 1.1 静态导入 1.2 简单例子 2. awaitility在RocketMQ中的实战 3. 总结 引言 在编写测试用例的时候遇到有异步或者队列处理的时候经常会用到 Thread.sleep() 等待来进行测试.例如:DLedger 测试选举的过程.当DLedger Leader下线.此时DLedger会重新发起选举,这个选举的过程是需要一定时间.很多时候在测试代码中就会使用 Thread.sleep . 由于选举需要的时间多少不确定所以sleep时

  • Golang 官方依赖注入工具wire示例详解

    目录 依赖注入是什么 开源选型 wire providers injectors 类型区分 总结 依赖注入是什么 Dependency Injection is the idea that your components (usually structs in go) should receive their dependencies when being created. 在 Golang 中,构造一个结构体常见的有两种方式: 在结构体初始化过程中,构建它的依赖: 将依赖作为构造器入参,传入进

  • 示例详解Python3 or Python2 两者之间的差异

    每门编程语言在发布更新之后,主要版本之间都会发生很大的变化. 在本文中,Vinodh Kumar 通过示例解释了 Python 2 和 Python 3 之间的一些重大差异,以帮助说明语言的变化. 本教程主要介绍内容: 表达式 Print 选项 Unequal 操作 Range 自动迁移 性能问题 主要的内部事务更改 1.表达式 在 Python 2 中为获得计算表达式,你会键入: 但在 Python 3 中,你会键入: 因此,无论我们输入什么,值都会分配给 2 和 3 中的变量 x.当在 Py

  • jvm垃圾回收之GC调优工具分析详解

    进行GC性能调优时, 需要明确了解, 当前的GC行为对系统和用户有多大的影响.有多种监控GC的工具和方法, 本章将逐一介绍常用的工具. JVM 在程序执行的过程中, 提供了GC行为的原生数据.那么, 我们就可以利用这些原生数据来生成各种报告.原生数据(raw data) 包括: 各个内存池的当前使用情况, 各个内存池的总容量, 每次GC暂停的持续时间, GC暂停在各个阶段的持续时间. 可以通过这些数据算出各种指标, 例如: 程序的内存分配率, 提升率等等.本章主要介绍如何获取原生数据. 后续的章

  • Go微服务项目配置文件的定义和读取示例详解

    目录 前言 场景 定义配置 配置文件 加载配置文件 实现原理 总结 项目地址 前言 我们在写应用时,基本都会用到配置文件,从各种 shell 到 nginx 等,都有自己的配置文件.虽然这没有太多难度,但是配置项一般相对比较繁杂,解析.校验也会比较麻烦.本文就给大家讲讲我们是怎么简化配置文件的定义和解析的. 场景 如果我们要写一个 Restful API 的服务,配置项大概有如下内容: Host,侦听的 IP,如果不填,默认用 0.0.0.0 Port,侦听的端口,必填,只能是数字,大于等于80

  • Kotlin 标准函数和静态方法示例详解

    目录 标准函数 with run Apply 定义静态方法 注解 顶层方法 标准函数 with with 的作用是可以在连续调用同一对象的多个方法时让代码变得更加精简 val result = with(obj){ //这里是obj的上下文 "value" //with 函数的返回值 } 看个例子,例如有一个水果列表,现在我们想吃完所有水果,并将结果打印出来 val list = listOf("Apple","Banana","Ora

随机推荐