No converter found for return value of type: class xxx(自定义的class对象)

先贴一份异常信息:

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 untypedValueSerializer(Class type) {
        ReadOnlyClassToSerializerMap.Bucket bucket = this._buckets[TypeKey.untypedHash(type) & this._mask];
        if (bucket == null) {
            return null;
        } else if (bucket.matchesUntyped(type)) {
            return bucket.value;
        } else {
            do {
                if ((bucket = bucket.next) == null) {
                    return null;
                }
            } while(!bucket.matchesUntyped(type));

            return bucket.value;
        }
    }

看到这里其在往下底层的源码我就没有深入挖掘了,各位有兴趣可以去看看。我试了一下当我将定义的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

 

你可能感兴趣的:(java,异常记录,Spring,SpringMVC,RequestBody注解,java,spring)