当前位置:
职场发展 > 我实习的时候连这么有问题的代码都写不出来!
我实习的时候连这么有问题的代码都写不出来!
这篇文章的内容是关于我最近刚刚遇到的一个问题。问题代码是我自己写的。我在编写单元测试时发现了这一点。我也自己修好了。修复后反思:我实习的时候连这么有问题的代码都写不出来。
这篇文章的内容是关于我最近刚刚遇到的一个问题。问题代码是我自己写的。我在编写单元测试时发现了这一点。我也自己修好了。修复后我反思了这个问题:实习期间连代码都不会写。
但我为什么要写下来呢?其实就是因为有些知识不是那么扎实,很容易被忽视,所以我在团队群里强调了这个问题:
因此,本文主要讨论BeanUtils工具的属性复制、深复制、浅复制等问题。好吧,我们先从正文开始吧。我们先介绍一下问题代码是什么,为什么会出现问题,是否适合修改?
在日常开发中,我们经常需要给对象赋值,通常会调用它们的set/get方法。有时候,如果我们要转换的两个对象的属性大致相同,我们就会考虑使用属性复制工具。
例如,我们经常在代码中将一个数据结构封装成DO、SDO、DTO、VO等,而这些bean中的大部分属性都是相同的,所以使用属性复制工具可以帮助我们节省大量的设置和操作。开始操作。
市面上类似的工具有很多,比较常用的有
1. Spring BeanUtils
2.Cglib BeanCopier
3.Apache BeanUtils
4. Apache PropertyUtils
5.推土机
6. 地图结构
这里我推荐大家使用MapStructs。我在《丢弃掉那些BeanUtils工具类吧,MapStruct真香!!!》介绍过原因。这里我就不详细说了。
最近我们有一个新的项目要创建一个新的应用程序,因为我自己分析了这些工具的效率,也看到了它们的实现原理。经过比较之后,我觉得MapStruct是最适合我们的,所以我在代码中编写了这个框架,在.
另外,由于Spring的BeanUtils使用起来比较方便,所以这两个框架主要用在需要beanCopy的代码中。
我们一般使用MapStruct来进行DO和DTO/Entity之间的转换,因为它可以指定一个单独的Mapper并自定义一些策略。
如果是同一个对象之间的复制(比如用一个DO创建一个新的DO),或者是两个完全不相关的对象的转换,就使用Spring的BeanUtils。一开始没有问题,但是后来在写单个测试的时候,发现了一个问题。
问题
我们先来看看我们在哪里使用Spring的BeanUtils
在我们的业务逻辑中,我们需要修改订单信息。进行变更时,我们不仅需要更新订单的上述属性信息,还需要创建变更管道。
更改管道记录更改前和更改后的数据,因此可用以下代码:
//从数据库查询当前订单并锁定 OrderDetail orderDetail = orderDetailDao.queryForLock(); //复制一个新的订单模型 OrderDetail newOrderDetail = new OrderDetail(); BeanUtils.copyProperties(orderDetail, newOrderDetail); //是的新订单模型执行修改后的逻辑操作 newOrderDetail.update(); //使用修改前的订单模型和修改后的订单模型组装订单更改流程 OrderDetailStream orderDetailStream = new OrderDetailStream(); orderDetailStream.create(orderDetail, newOrderDetail);
大致逻辑是这样的,因为创建订单变更管道时,需要一个变更前的订单和一个变更后的订单。于是我们想到创建一个新的订单模型,然后运行新的订单模型,避免影响旧的订单模型。
然而这个BeanUtils.copyProperties过程其实是有问题的。
因为BeanUtils复制属性时,本质上是浅复制,而不是深复制。
浅拷贝?深拷贝?
什么是浅拷贝和深拷贝?让我们看一下这些概念。
1.浅拷贝:按值传输基本数据类型,以及像按引用传输一样复制引用数据类型。这是一个浅拷贝。
2.深拷贝:按值传输基本数据类型,并为引用数据类型创建一个新对象并复制其内容。这是一个深拷贝。
我们通过一个实际的例子来看看为什么我说BeanUtils.copyProperties的过程是浅拷贝。
首先定义两个类:public class Address { private String 省;私人字符串城市;私有字符串区域; // 省略构造函数和 setter/getter } class User { private String name;私有字符串密码;私人地址; //省略构造函数和setter/getter }
然后编写测试代码:
用户 user = new User("Hollis", "holliscuan"); user.setAddress(new Address("浙江", "杭州", "滨江"));用户 newUser = 新用户(); BeanUtils.copyProperties(用户, newUser); System.out.println(user.getAddress() == newUser.getAddress());
上述代码的输出结果为:true
即BeanUtils.copyProperties复制的newUser中的地址对象与原用户中的地址对象是同一个对象。
您可以尝试修改newUser中的地址对象:
newUser.getAddress().setCity("上海"); System.out.println(JSON.toJSONString(用户)); System.out.println(JSON.toJSONString(newUser));
输出结果:{"地址":{"地区":"滨江","城市":"上海","省份":"浙江"},"姓名":"霍利斯","密码":"霍利斯"} {"地址":{"地区":"滨江","城市":"上海","省份":"浙江"},"名称":"霍利斯","密码":"霍利斯创"}
可以发现,原来的对象也受到了修改的影响。
这就是所谓的浅拷贝!
如何进行深拷贝
发现问题之后,我们就要想办法解决,那么如何实现深拷贝呢?
1.实现Cloneable接口,重写clone()
Object 类中定义了克隆方法。这个方法实际上是一个浅拷贝,没有重写它。
如果要实现深拷贝,就需要重写clone方法,而如果要重写clone方法,就必须实现Cloneable,否则会报CloneNotSupportedException。
修改上面的代码,重写clone方法:公共类地址实现Cloneable{私有字符串省;私人字符串城市;私有字符串区域; // 省略构造函数和 setter/getter @Override public Object clone() throws CloneNotSupportedException { return super.clone(); 。 { 私有字符串名称;私有字符串密码;私人地址地址; //省略构造函数和setter/getter @Override protected Object clone() throws CloneNotSupportedException { User user = ( User)super.clone(); user.setAddress((地址)address.clone());返回用户;
之后执行上面的测试代码,可以发现此时newUser中的address对象是一个新的对象。
这个方法可以实现深拷贝,但是问题是如果我们User中的对象很多的话,clone方法就会很长,而且如果后面有修改,User中增加了新的属性,这个地方也需要改。
那么,有没有什么方法可以一劳永逸,无需修改呢?
2.序列化实现深拷贝
我们可以借助序列化来实现深拷贝。先把对象序列化成流,然后再从流中反序列化成对象,所以一定是一个新对象。
序列化的方法有很多种。例如,我们可以使用各种JSON工具将对象序列化为JSON字符串,然后从字符串反序列化为对象。
如果使用fastjson实现:User newUser = JSON.parseObject(JSON.toJSONString(user), User.class);
深度复制也是可能的。
此外,您还可以使用Apache Commons Lang中提供的SerializationUtils工具。
我们需要修改上面的User和Address类,让它们实现Serialized接口,否则无法序列化。
类用户实现可序列化类地址实现可序列化
然后当你需要复制时:
用户 newUser = (用户) SerializationUtils.clone(用户);
同样的,也可以实现深拷贝~!
总结
我们在使用各种BeanUtil的时候,一定要注意是浅拷贝还是深拷贝。浅拷贝的结果是两个对象中的引用对象具有相同的地址。只要有变化,就会受到影响。
实现深拷贝的方式有很多种,其中比较常用的是实现Cloneable接口并重写clone方法,以及使用序列化+反序列化的方式创建新对象。
好了,今天就这些了。