最近在工作中遇到了这样一个问题:一个后端接口,请求对象中有一个字段List
然而实际运行中却报错了:
本地测试的时候发现这个报错上面有两行warn告警信息:
基本可以判断是jackson在数据绑定的时候由于有多个set方法造成了冲突,经验证的确如此。
那么,为什么呢?(spring boot version:2.0.6.RELEASE, jackson version: 2.9.9)
为什么Spring boot在进行数据自动绑定的时候不支持set方法重载?为什么Jackson在进行数据绑定的时候对于重载set方法会报冲突?fastjson有这个问题吗?
带着上面这些疑问,我简单的写了个demo进行调试,追踪上述异常出现的原因。
上述问题实际上就是jackson进行对象反序列化时set方法冲突问题,简单处理,直接写一个main方法进行调试:
直接运行,报错:
进入POJOPropertyBuilder.java,发现是getSetter()方法抛出的异常。阅读getSetter()源码,
public AnnotatedMethod getSetter() {
POJOPropertyBuilder.Linked curr = this._setters;
if (curr == null) {
return null;
} else {
POJOPropertyBuilder.Linked next = curr.next;
A if (next == null) {
return (AnnotatedMethod)curr.value;
B } else {
while(true) {
if (next == null) {
this._setters = curr.withoutNext();
return (AnnotatedMethod)curr.value;
}
label42: {
Class> currClass = ((AnnotatedMethod)curr.value).getDeclaringClass();
Class> nextClass = ((AnnotatedMethod)next.value).getDeclaringClass();
if (currClass != nextClass) {
if (currClass.isAssignableFrom(nextClass)) {
curr = next;
break label42;
}
if (nextClass.isAssignableFrom(currClass)) {
break label42;
}
}
AnnotatedMethod nextM = (AnnotatedMethod)next.value;
AnnotatedMethod currM = (AnnotatedMethod)curr.value;
int priNext = this._setterPriority(nextM);
int priCurr = this._setterPriority(currM);
if (priNext != priCurr) {
if (priNext < priCurr) {
curr = next;
}
} else {
if (this._annotationIntrospector == null) {
break;
}
C AnnotatedMethod pref = this._annotationIntrospector.resolveSetterConflict(this._config, currM, nextM);
D if (pref != currM) {
if (pref != nextM) {
break;
}
curr = next;
}
}
}
next = next.next;
}
throw new IllegalArgumentException(String.format("Conflicting setter definitions for property \"%s\": %s vs %s", this.getName(), ((AnnotatedMethod)curr.value).getFullName(), ((AnnotatedMethod)next.value).getFullName()));
}
}
}
发现getSetter()的作用就是确认某个参数对应的set方法,如果有多个,会进行冲突处理。如图,行A代表只有一个set方法,直接返
回,行B进行多个set方法处理---这里可以看出,Jackson是支持多set方法的,重点方法在行C:
AnnotatedMethod pref = this._annotationIntrospector.resolveSetterConflict(this._config, currM, nextM);
public AnnotatedMethod resolveSetterConflict(MapperConfig> config, AnnotatedMethod setter1, AnnotatedMethod setter2) {
Class> cls1 = setter1.getRawParameterType(0);
Class> cls2 = setter2.getRawParameterType(0);
if (cls1.isPrimitive()) {
if (!cls2.isPrimitive()) {
return setter1;
}
} else if (cls2.isPrimitive()) {
return setter2;
}
if (cls1 == String.class) {
if (cls2 != String.class) {
return setter1;
}
} else if (cls2 == String.class) {
return setter2;
}
return null;
}
从上面方法可以看出,Jackson在进行set方法冲突解决的时候是根据方法请求参数的类型是否基本类型以及String类型来选择set方法,和具体的待绑定的请求数据的类型无关。到这里,基本可以知道例子中异常的原因:Integer 和 List都不是基本数据类型,因此该方法返回null,回到getSetter()方法中,D行,pref != currM && pref != nextM, 退出循环,抛出异常。
有意思的是,这里的_setters中拿到的set方法的顺序是不固定的,因此假设再增加一个setStatus(String s)方法,多次调用,有可能会成功。这点有兴趣的同学可以自己尝试一下。另外,子类的情况大家也可以尝试一下。
针对这种情况,如果切实有需要重载set方法的需求,可以考虑使用泛型:
private List status;
public void setStatus(T object){
if(object instanceof String){
this.status = Arrays.asList(((String)object).split(",")).stream().map(Integer::parseInt).collect(Collectors.toList());
}else if(object instanceof List){
status = (List)object;
}else if(object instanceof Integer){
status = Arrays.asList((Integer) object);
}
}
这里还有一个疑问,Jackson处理冲突的时候为什么不根据反序列化的数据的类型进行set方法的匹配?
最后,fastjson中方法重载不起作用。