复制个对象,程序就炸了! | Java Debug 笔记

复制个对象,程序就炸了! | Java Debug 笔记

技术杂谈小彩虹2021-07-19 8:42:46170A+A-

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看活动链接

一 事故背景

这次的故事要从一个紧急需求说起,产品阿产跑过来说,业务那边打单觉得少了点味道,要加一个打印时间,ok,没啥问题,就是一个小改动,但是却发生了意想不到的惊喜!

二 事故现场

客服反馈,客户那边一批人打电话说单据无法打印了,于是开始了紧急的问题排查

  • 产品 已到达战场
  • 项目经理 还有30s到达战场
  • 技术总监还有30s到达战场
  • 哼哼哈嘿,哼哼哈嘿

三 定位问题

明明就进行了一次小改动,怎么就这么爆炸。通过日志,发现了这样一段报错

Caused by: java.lang.IllegalArgumentException: argument type mismatch
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.inspireso.framework.util.Transform.copy(Transform.java:316)
        ... 2 more

argument type mismatch 这是啥,他为啥出来作妖,我们看到报错信息是反射的问题,报错的方法是这个Transform.copy(Transform.java:316)

四 代码欣赏

private Map<String, Object> buildRuleVariables(EdiBooking booking) {
    Map<String, Object> vars = Maps.newHashMap();
    EdiBooking newBooking = new EdiBooking();
    Transform.copy(booking, newBooking);
    newBooking.setPrintedDate(new Date());
    vars.put("EIR", newBooking);
    return vars;
}

就是一个复制方法,怎么就搞出来一个炸弹呢?看下他里面写的什么

  public static <TSource, TTarget> TTarget copy(TSource source, TTarget target, boolean ignoreNullValue, boolean ignoreCollectionProperty) {
        Preconditions.checkNotNull(source, "Source can not be null!");
        Preconditions.checkNotNull(target, "Target can not be null!");
        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        PropertyDescriptor[] arr$ = targetPds;
        int len$ = targetPds.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            PropertyDescriptor targetPd = arr$[i$];
            if (targetPd.getWriteMethod() != null) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null && sourcePd.getReadMethod() != null && (!ignoreCollectionProperty || !Collection.class.isAssignableFrom(sourcePd.getPropertyType())) && sourcePd.getPropertyType().isAssignableFrom(targetPd.getPropertyType())) {
                    try {
                        //获取读取属性的方法
                        Method readMethod = sourcePd.getReadMethod();
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }

                        Object value = readMethod.invoke(source);
                        if (value != null || !ignoreNullValue) {
                            readMethod = targetPd.getReadMethod();
                            if (!Objects.equal(value, readMethod.invoke(target))) {
                                Method writeMethod = targetPd.getWriteMethod();
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                // 通过读取属性的方法 传入目标值,现在的值
                                writeMethod.invoke(target, value);
                            }
                        }
                    } catch (Throwable var14) {
                        throw new FatalBeanException("Could not copy properties from source to target", var14);
                    }
                }
            }
        }

        return target;
    }

Method readMethod = sourcePd.getReadMethod(); 方法正常来说他应该返回属性的getPrinted()方法,但是他返回的却是isPrinted()方法,isPrinted返回的Boolean而不是Integer最终导致了悲剧

五 罪魁祸首

public Integer getPrinted() {
    return printed;
}
public void setPrinted(Integer printed) {
    if (printed == null) {
        printed = 0;
    }
    this.printed = printed;
}


public boolean isPrinted() {
    if (this.getPrinted() == null) {
        return false;
    } else {
        return 1 == this.getPrinted();
    }
}

六 本地调试核查

/**
 * @author 子羽
 * @Description TODO
 * @Date 2021/5/11
 */
public class TestEntity {
    public static void main(String[] args) {
        EdiBooking booking = new EdiBooking();
        booking.setPrinted(1);
        booking.setAlarm(2);
        EdiBooking newBooking = new EdiBooking();
        Transform.copy(booking, newBooking);
        newBooking.setPrintedDate(new Date());
        System.out.println(JSON.toJSON(newBooking));
    }
}

七 解决方法

  • 使用Spring自带的复制方法,BeanUtils.copyProperties(booking, newBooking) 虽然使用了这个方法没有报错,但是通过json序列化发现,里面居然没有了printed属性的值,所以是不可取的

      {
          "useRule":"",
          "falsePrintFlag":0,
          "version":1,
          "printedDate":1620737758897,
          "sendCdStatus":0,
          "applyInvoiceStatus":0,
          "leak":0,
          "free":false,
          "printedType":0,
          "empty":false,
          "hold":false,
          "new":true,
          "remark":"",
          "lockStatus":0,
          "orgCode":"SUNISCO",
          "alarm":2,
          "emptyStatus":"F",
          "dangerFlag":false,
          "payStatus":0
      }
    
  • 移除掉isPrinted()方法,在实力类属性字段里面一定不要写is方法,移除后解决问题

八 结语

记住,实体类映射字段,一定不要写is方法,否则对于一些框架而言,他可能分不清该用那个方法来获取字段的值,上面就是一个活生生的栗子。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们