本文的内容是最近我刚刚遇到的一个问题,问题代码是我自己写的,也是我自己写单元测试的时候发现的,也是我自己修复的,修复完之后,我反思了一下:这样的问题代码,我实习的时候都写不出来。
可是为什么我就写出来了呢?其实还是因为有些知识没那么扎实了~就容易被忽略了,于是我在团队群里面强调了一下这个问题:
所以,本文主要是关于beanutils工具的属性拷贝以及深拷贝、浅拷贝等问题的。好了开始正文,介绍下问题代码是什么,为什么有问题,又符合修改?
在日常开发中,我们经常需要给对象进行赋值,通常会调用其set/get方法,有些时候,如果我们要转换的两个对象之间属性大致相同,会考虑使用属性拷贝工具进行。
如我们经常在代码中会对一个数据结构封装成do、sdo、dto、vo等,而这些bean中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的set和get操作。
市面上有很多类似的工具类,比较常用的有
1、spring beanutils
2、cglib beancopier
3、apache beanutils
4、apache propertyutils
5、dozer
6、mapstucts
这里面我比较建议大家使用的是mapstructs。
最近我们有个新项目,要创建一个新的应用,因为我自己分析过这些工具的效率,也去看过他们的实现原理,比较下来之后,我觉得mapstruct是最适合我们的,于是就在代码中引入了这个框架。
另外,因为spring的beanutils用起来也比较方便,所以,代码中对于需要beancopy的地方主要在使用这两个框架。
我们一般是这样的,如果是do和dto/entity之间的转换,我们统一使用mapstruct,因为他可以指定单独的mapper,可以自定义一些策略。
如果是同对象之间的拷贝(如用一个do创建一个新的do),或者完全不相关的两个对象转换,则使用spring的beanutils。
刚开始都没什么问题,但是后面我在写单测的时候,发现了一个问题。
问题
先来看看我们是在什么地方用的spring的beanutils
我们的业务逻辑中,需要对订单信息进行修改,在更改时,不仅要更新订单的上面的属性信息,还需要创建一条变更流水。
而变更流水中同时记录了变更前和变更后的数据,所以就有了以下代码:
//从数据库中查询出当前订单,并加锁 orderdetail orderdetail = orderdetaildao.queryforlock(); //copy一个新的订单模型 orderdetail neworderdetail = new orderdetail();
beanutils.copyproperties(orderdetail, neworderdetail);//对新的订单模型进行修改逻辑操作
neworderdetail.update(); //使用修改前的订单模型和修改后的订单模型组装出订单变更流水
orderdetailstream orderdetailstream = new orderdetailstream(); orderdetailstream.create(orderdetail, neworderdetail);
大致逻辑是这样的,因为创建订单变更流水的时候,需要一个改变前的订单和改变后的订单。所以我们想到了要new一个新的订单模型,然后操作新的订单模型,避免对旧的有影响。
但是,就是这个beanutils.copyproperties的过程其实是有问题的。
因为beanutils在进行属性copy的时候,本质上是浅拷贝,而不是深拷贝。
浅拷贝?深拷贝?
什么是浅拷贝和深拷贝?来看下概念。
1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
我们举个实际例子,来看下为啥我说beanutils.copyproperties的过程是浅拷贝。
先来定义两个类:
public class address { private string province; private string city; private string area; //省略构造函数和setter/getter } class user { private string name; private string password; private address address; //省略构造函数和setter/getter }
然后写一段测试代码:
user user = new user(“hollis”, “hollischuang”); user.setaddress(new address(“zhejiang”, “hangzhou”, “binjiang”)); user newuser = new user(); beanutils.copyproperties(user, newuser); system.out.println(user.getaddress() == newuser.getaddress());
以上代码输出结果为:true
即,我们beanutils.copyproperties拷贝出来的newuser中的address对象和原来的user中的address对象是同一个对象。
可以尝试着修改下newuser中的address对象:
newuser.getaddress().setcity(“shanghai”); system.out.println(json.tojsonstring(user)); system.out.println(json.tojsonstring(newuser));
输出结果:
{“address”:{“area”:“binjiang”,“city”:“shanghai”,“province”:“zhejiang”},“name”:“hollis”,“password”:“hollischuang”} {“address”:{“area”:“binjiang”,“city”:“shanghai”,“province”:“zhejiang”},“name”:“hollis”,“password”:“hollischuang”}
可以发现,原来的对象也受到了修改的影响。
这就是所谓的浅拷贝!
如何进行深拷贝
发现问题之后,我们就要想办法解决,那么如何实现深拷贝呢?
1、实现cloneable接口,重写clone()
在object类中定义了一个clone方法,这个方法其实在不重写的情况下,其实也是浅拷贝的。
如果想要实现深拷贝,就需要重写clone方法,而想要重写clone方法,就必须实现cloneable,否则会报clonenotsupportedexception异常。
将上述代码修改下,重写clone方法:
public class address implements cloneable{ private string province; private string city; private string area; //省略构造函数和setter/getter@override public object clone() throws clonenotsupportedexception { return super.clone();
} } class user implements cloneable{ private string name; private string password; private address address; //省略构造函数和setter/getter @override protected object clone() throws clonenotsupportedexception { user user = (user)super.clone();
user.setaddress((address)address.clone()); return user; } }
之后,在执行一下上面的测试代码,就可以发现,这时候newuser中的address对象就是一个新的对象了。
这种方式就能实现深拷贝,但是问题是如果我们在user中有很多个对象,那么clone方法就写的很长,而且如果后面有修改,在user中新增属性,这个地方也要改。
那么,有没有什么办法可以不需要修改,一劳永逸呢?
2、序列化实现深拷贝
我们可以借助序列化来实现深拷贝。先把对象序列化成流,再从流中反序列化成对象,这样就一定是新的对象了。
序列化的方式有很多,比如我们可以使用各种json工具,把对象序列化成json字符串,然后再从字符串中反序列化成对象。
如使用fastjson实现:
user newuser = json.parseobject(json.tojsonstring(user), user.class);
也可实现深拷贝。
除此之外,还可以使用apache commons lang中提供的serializationutils工具实现。
我们需要修改下上面的user和address类,使他们实现serializable接口,否则是无法进行序列化的。
class user implements serializable class address implements serializable
然后在需要拷贝的时候:
user newuser = (user) serializationutils.clone(user);
同样,也可以实现深拷贝啦~!
总结
当我们使用各类beanutils的时候,一定要注意是浅拷贝还是深拷贝,浅拷贝的结果就是两个对象中的引用对象都是同一个地址,只要发生改变,都会有影响。
想要实现深拷贝,有很多种办法,其中比较常用的就是实现cloneable接口重写clone方法,还有使用序列化+反序列化创建新对象。
好了,以上就是今天的全部内容了。
半导体测试—优质好文推荐
亚马逊图像识别技术无法可靠地辨别女性和深肤色人群
2018年被称为高镍811的量产元年,高能量密度、低成本成未来趋势
检测软包电池芯厚度的几种办法
要保证自动驾驶的安全性需要多少传感器?
为什么有时候会写出烂代码
声频系统在手机与PDA 中的应用与设计
看微软如何在十年后首次反超苹果,再次成为全球市值最高的公司
计算芯片的两个市场,MPU 与 MCU 之间是否有明确的界限?
叫板 Android ,跨平台应用开发神器 Flutter 又添开源插件
Semtech Corporation推出其新一代的LoRa器件和无线射频技术芯片组
应用无线鼠标中的2.4GHz无线收发芯片
iPhone 12在美畅销,占iPhone总量的27%
中国电信提出了云网一体和全面云化发展战略
激光探测让智慧医疗更进一步 将“听”到癌细胞
国内EDA企业大盘点
FIR数字滤波器的FPGA实现研究策略
瑞萨电子携其最新解决方案亮相2018上海慕尼黑电子展,加速自动驾驶、智能家居等领域的智能化创新发展
使用Python来收集、处理和可视化人口数据
封测厂芯片出货数量创下新高