金蝶云,搜索引擎优化包括哪些方面,网站买卖,提高网站建设管理水平今天想写一篇文章#xff0c;主要关于深拷贝和浅拷贝相关的#xff0c;主要是最近写代码的时候遇到一个BUG#xff0c;刚好涉及到浅拷贝导致的问题。 问题背景 现在有一个需要是需要修改门店信息#xff0c;门店也区分父门店和子门店#xff0c;父门店被编辑更新是需要通过… 今天想写一篇文章主要关于深拷贝和浅拷贝相关的主要是最近写代码的时候遇到一个BUG刚好涉及到浅拷贝导致的问题。 问题背景 现在有一个需要是需要修改门店信息门店也区分父门店和子门店父门店被编辑更新是需要通过到第三方的然后之前是没有父子门店的概念的后面新增的需求然后editShop这个方法的入参就是关于门店的信息么这里我简化它的参数但是保留了一个data属于引用型参数。 下面的模拟当时出现BUG的代码
public class RequestDto {MapString, Object data new HashMap();private Integer status ;private Long id;public MapString, Object getData() {return data;}
}public class ShopService {public void editShop(RequestDto requestDto) {System.out.println(入参 requestDto.toString());// 操作// 父门店获取子门店假设有两个ListLong childShops getChildShop(requestDto.getId());childShops.stream().forEach(childShop - {RequestDto requestDto1 new RequestDto();BeanUtils.copyProperties(requestDto, requestDto1);requestDto1.setId(childShop);requestDto1.getData().put(isSync, Boolean.FALSE);editShop(requestDto1);sync(childShop);});System.out.println(返回前 requestDto.toString());if (Boolean.FALSE.equals(requestDto.getData().get(isSync))) {return;}sync(requestDto.getId());}private ListLong getChildShop(Long parentId) {if (parentId.equals(1L)) {// 父门店ListLong childShopIds new ArrayList();childShopIds.add(123L);childShopIds.add(234L);return childShopIds;} else {return new ArrayList();}}private void sync(Long shopId) {System.out.println(同步门店第三方 shopId);}
}
public class Main {public static void main(String[] args) {ShopService shopService new ShopService();RequestDto requestDto new RequestDto();requestDto.setId(1L);requestDto.setStatus(0);shopService.editShop(requestDto);}}
模拟了当时操作父门店时需要触发子门店的配置信息同步通过递归来实现似乎是不错的选择但是将isSync作为递归结束的标志来然后就可以完成配置的复制和更新看起来是没有什么问题的但是当这个代码真的到达线上的时候发现编辑父门店子门店触发通过不了但是父门店就触发不了同步了在判断的if里面就退出了导致线上发现父门店配置没有同步的BUG。然后经过一顿头发输出看了好几遍日志之后也没有发现问题的所在后面本地起了一个调试才发现这个问题是出自于BeanUtils.copyProperties的问题排查出来了。
BeanUtils.copyProperties的时候已经将requestDto1中data的引用指向了requestDto的引用然后对它添加isSync的时候是会导致父门店的dto也同时被修改了。 BeanUtils.copyProperties的源码分析
private static void copyProperties(Object source, Object target, Nullable Class? editable,Nullable String... ignoreProperties) throws BeansException {Assert.notNull(source, Source must not be null);Assert.notNull(target, Target must not be null);Class? actualEditable target.getClass();if (editable ! null) {if (!editable.isInstance(target)) {throw new IllegalArgumentException(Target class [ target.getClass().getName() ] not assignable to Editable class [ editable.getName() ]);}actualEditable editable;}// 获取所有的属性PropertyDescriptor[] targetPds getPropertyDescriptors(actualEditable);ListString ignoreList (ignoreProperties ! null ? Arrays.asList(ignoreProperties) : null);// 遍历for (PropertyDescriptor targetPd : targetPds) {// 写入的set方法Method writeMethod targetPd.getWriteMethod();if (writeMethod ! null (ignoreList null || !ignoreList.contains(targetPd.getName()))) {// source对象对应的的属性PropertyDescriptor sourcePd getPropertyDescriptor(source.getClass(), targetPd.getName());if (sourcePd ! null) {Method readMethod sourcePd.getReadMethod();if (readMethod ! null) {ResolvableType sourceResolvableType ResolvableType.forMethodReturnType(readMethod);ResolvableType targetResolvableType ResolvableType.forMethodParameter(writeMethod, 0);// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.boolean isAssignable (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :targetResolvableType.isAssignableFrom(sourceResolvableType));if (isAssignable) {try {// 设置成可访问防止私有if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}// 赋值Object value readMethod.invoke(source);if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {writeMethod.setAccessible(true);}writeMethod.invoke(target, value);}catch (Throwable ex) {throw new FatalBeanException(Could not copy property targetPd.getName() from source to target, ex);}}}}}}}然后这里我们可以看到它最后也是通过反射的invoke方法来进行set设置值而value是从之前的source获取所以获取的是它的引用所以用的是浅引用。
浅拷贝和深拷贝
浅拷贝从上面的例子应该是非常容易就可以理解了就是浅拷贝除了拷贝基础数据类型及其包装类型外在对引用类型的拷贝时是直接拷引用在Java中就是相当于是同一个的对象的引用。
深拷贝的话就是相当于创建一个新的一模一样的对象但是这个对象的内存地址是不一致的。
在Java里面实现深拷贝的方式有三种。 通过直接创建赋值set 通过序列化和反序列化创建的对象是属于重新生成的对象也是属于深拷贝。 通过fastjson这样的json反序列化也是实现深拷贝的一种方式。 通过实现重写父类的clone方法也可以实现。 借此记录下自己踩到的BUG虽然不是很难的东西写出来也是希望下一次遇到这个问题的时候能够更快的解决