先贴一份异常信息:
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class org.jackson.mvc.web.po.RequestInfo
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:220)
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:181)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:82)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:123)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
对应的代码如下:
package org.jackson.mvc.web.po;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
public class RequestInfo implements Serializable {
private String username;
private Long userId;
private int userAge;
private double userHeight; // 身高
private Date birthday;
private BigDecimal balance; // 余额
}
package org.jackson.mvc.web.controllers.hello;
import org.jackson.mvc.web.po.RequestInfo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "v1/hello/")
public class HelloController {
@PostMapping(path = "showRequestInfo")
public RequestInfo showRequestInfo(@RequestBody RequestInfo requestInfo) {
return requestInfo;
}
}
请求参数如下:
### Send POST request with json body
POST http://localhost:9090/v1/hello/showRequestInfo
Content-Type: application/json
{
"username" : "java",
"userId" : "123455",
"userAge" : "23",
"userHeight" : "170.5",
"birthday" : "1990-12-31 23:59:59",
"balance" : "20.55"
}
其实看到我定义的实体对象,基本上就可以看出一些端倪来了,我定义的实体类没有对应属性的get和set方法。而这正是问题的根源所在。解决方法就是将对应的属性添加get和set方法。下面来看看是什么原因导致的?
根据异常信息,查看Spring源码,抛出异常的代码如下:
#org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters
######
HttpServletRequest request = inputMessage.getServletRequest();
List acceptableTypes = getAcceptableMediaTypes(request);
List producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
通过调试发现返回的body是一个空的对象(这里的空对象不是null,是指一个已经创建的对象,只是属性都是初始值,因为没有set方法,无法将请求中的参数进行赋值)。那就是另一个条件满足了。查看这个获取producibleTypes的方法,发现是通过注册的HttpMessageConverter和传递的参数类型查找可用的MediaType。
List producibleTypes = getProducibleMediaTypes(request, valueType, targetType)
通常默认的Spring容器中注册的MessageConverter有如下九种:
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
ResourceRegionHttpMessageConverter
AllEncompassingFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJackson2HttpMessageConverter
MappingJackson2CborHttpMessageConverter
当我们的参数一个POJO对象时,正常而言会匹配到MappingJackson2HttpMessageConverter,但是当定义的POJO对象属性都没有get和set方法时,该对象的canWrite方法的判断就会返回false。其源码如下:
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#canWrite
public boolean canWrite(Class> clazz, @Nullable MediaType mediaType) {
if (!canWrite(mediaType)) {
return false;
}
if (mediaType != null && mediaType.getCharset() != null) {
Charset charset = mediaType.getCharset();
if (!ENCODINGS.containsKey(charset.name())) {
return false;
}
}
AtomicReference causeRef = new AtomicReference<>();
if (this.objectMapper.canSerialize(clazz, causeRef)) {
return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}
继续跟踪该方法中this.objectMapper.canSerialize(clazz, causeRef),最后发现在下述方法中返回一个ReadOnlyClassToSerializerMap.Bucket
com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap#untypedValueSerializer(java.lang.Class>)
public JsonSerializer
看到这里其在往下底层的源码我就没有深入挖掘了,各位有兴趣可以去看看。我试了一下当我将定义的POJO对象中至少一个属性设置get和set方法后,请求可正常返回,但是只返回有get和set方法的属性。
例如修改为如下代码:
package org.jackson.mvc.web.po;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
public class RequestInfo implements Serializable {
private String username;
private Long userId;
private int userAge;
private double userHeight; // 身高
private Date birthday;
private BigDecimal balance; // 余额
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
请求响应结果如下:
POST http://localhost:9090/v1/hello/showRequestInfo
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 31 Dec 2020 19:32:59 GMT
Keep-Alive: timeout=20
Connection: keep-alive
{
"username": "java"
}
Response code: 200; Time: 235ms; Content length: 19 bytes
注意。以上情况均基于你的应用引入jackson相关的依赖。我使用的jackson的版本依赖信息如下:
2.11.2
com.fasterxml.jackson.core
jackson-annotations
${jackson.version}
com.fasterxml.jackson.core
jackson-core
${jackson.version}
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
缺少以上三个jar包时,我这边遇到的提示如下信息:
HTTP状态 415 - 不支持的媒体类型
类型 状态报告
描述 源服务器拒绝服务请求,因为有效负载的格式在目标资源上此方法不支持。
Apache Tomcat/9.0.41