背景
最近在项目上开发接口时,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。