[踩坑] @RequestBody注解转换modal报400错误问题排查与解决

背景

最近在项目上开发接口时,controller接口选择用application/json方式接收参数,接口入参用了@RequestBody。简化controller和modal如下所示。

@RestController("/jsonModal")
public class JsonModalController {

    @PostMapping
    public Object json(@RequestBody JsonModal jsonModal) {
        return ResultAssistant.ok(jsonModal);
    }
}

public class JsonModal {

private String id;

private String name;

private int status;

public JsonModal(String id, String name, int status) {
this.id = id;
this.name = name;
this.status = status;
}

  ... 省略get,set
}

但是最终调试接口的时候总是报400状态码错误。

排查与解决

一开始以为是转换json字符串的格式不对,但后面用了fastJson进行json字符串转换后仍旧是400错误。

既然不是json字符串的问题,那就换个思路排查,首先需要找到具体的Exception。通过在项目的GlobalExceptionHandler的排查最终确定了此次异常为HttpMessageNotReadableException。

那么错误找到了,就开始分析为什么会出现这个错误,从该错误的注解来看,这个错误主要是在消息转换的出现错误的时候抛出的。

/**
 * Thrown by {@link HttpMessageConverter} implementations when the
 * {@link HttpMessageConverter#read} method fails.
 *
 * @author Arjen Poutsma
 * @since 3.0
 */
@SuppressWarnings("serial")
public class HttpMessageNotReadableException extends HttpMessageConversionException

那么接下来的事情自然就是打断点,跟踪排查了为什么在转换的时候会出现这个不可读的异常。通过断点追踪最后在jackson下的一个类解析器BeanDeserializerBase中发现如下的消息。

    protected Object deserializeFromObjectUsingNonDefault(JsonParser p,
            DeserializationContext ctxt) throws IOException
    {
        final JsonDeserializer<Object> delegateDeser = _delegateDeserializer();
        if (delegateDeser != null) {
            return _valueInstantiator.createUsingDelegate(ctxt,
                    delegateDeser.deserialize(p, ctxt));
        }
        if (_propertyBasedCreator != null) {
            return _deserializeUsingPropertyBased(p, ctxt);
        }
        // should only occur for abstract types...
        if (_beanType.isAbstract()) {
            return ctxt.handleMissingInstantiator(handledType(), p,
                    "abstract type (need to add/enable type information?)");
        }
        // 25-Jan-2017, tatu: We do not actually support use of Creators for non-static
        //   inner classes -- with one and only one exception; that of default constructor!
        //   -- so let's indicate it
        Class<?> raw = _beanType.getRawClass();
        if (ClassUtil.isNonStaticInnerClass(raw)) {
            return ctxt.handleMissingInstantiator(raw, p,
"can only instantiate non-static inner class by using default, no-argument constructor");
        }
        return ctxt.handleMissingInstantiator(raw, p,
"no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)");
    }

这个方法主要就是反序列化一个没有默认构造函数的bean,注意到后面注释之后的东西其实就已经说明了本次的问题了。

We do not actually support use of Creators for non-static inner classes -- with one and only one exception; that of default constructor! -- so let's indicate it

所以其实使用@RequestBody注解的参数其实是不支持使用静态内部class或者是一个没有默认构造函数的modal。

回到我自身使用的问题,可以看到我的modal是只有一个全参数构造函数而没有默认空构造函数,所以解决方案就是加一个空构造函数,问题即可解决。

总结

当使用@RequestBody注解入参的时候,记得注解后面紧跟的参数不要是一个static inner class或者是一个缺少空构造函数的class

posted @ 2018-09-07 16:12  magotzis  阅读(826)  评论(0编辑  收藏  举报