本文正在参加「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方法,否则对于一些框架而言,他可能分不清该用那个方法来获取字段的值,上面就是一个活生生的栗子。