Spring boot请求参数数据绑定重载set方法冲突异常原因解析

最近在工作中遇到了这样一个问题:一个后端接口,请求对象中有一个字段List<Integer> status,有两个地方调用该接口,其中一个传参status:[1,2],其中一个传参status:1。前一个接口调用没问题,后一个接口调用报错,因为类型不匹配。最开始,出于不需要前端页面同步修改考虑,直接对对象中的status字段进行了set方法重载,如下:

然而实际运行中却报错了:

本地测试的时候发现这个报错上面有两行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<AnnotatedMethod> curr = this._setters;
        if (curr == null) {
            return null;
        } else {
            POJOPropertyBuilder.Linked<AnnotatedMethod> 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<Integer> status;

    public <T> 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中方法重载不起作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章