使用RequestBody注解传递POJO对象时返回HTTP状态码415 - 不支持的媒体类型

参考地址:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc

源码地址(持续更新中):https://gitee.com/qinshizhang/spring-mvc-learn

入参的POJO对象如下:(注:以下内容都是Spring MVC基于注解的Java配置,而非基于xml配置文件开发)

public class RequestInfo implements Serializable {
    private String username;
    private Long userId;
    private int userAge;
    private double userHeight; // 身高
    private Date birthday;
    private BigDecimal balance; // 余额

    // 为了不占篇幅,省略了get和set方法
}

Controller层代码如下:

@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"
}

请求响应结果如下:(部分)

HTTP状态 415 - 不支持的媒体类型


类型 状态报告

描述 源服务器拒绝服务请求,因为有效负载的格式在目标资源上此方法不支持。


Apache Tomcat/9.0.41

 跟踪代码调试,发现抛出异常的地方在如下方法中:

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)

抛异常的代码块:


if (body == NO_VALUE) {
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
		}
 

检查其注册的HttpMessageConverter列表(this.messageConverters)结果如下:(没有Json格式的MessageConverter)

0 = {ByteArrayHttpMessageConverter@5536} 
1 = {StringHttpMessageConverter@5537} 
2 = {ResourceHttpMessageConverter@5538} 
3 = {ResourceRegionHttpMessageConverter@5539} 
4 = {SourceHttpMessageConverter@5540} 
5 = {AllEncompassingFormHttpMessageConverter@5541} 
6 = {Jaxb2RootElementHttpMessageConverter@5542}  

而源码中this.messageConverters这个列表是如何初始化的?默认会初始化那些MessageConverter的实现类?我们使用Spring MVC时,使用@Configuration和@EnableWebMvc注解开启Spring MVC的使用。例如配置如下:

@Configuration
@ComponentScan(basePackages = {"org.jackson.mvc.web"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {WebFilter.class})})
@EnableWebMvc
public class ServletConfig implements WebMvcConfigurer {

}

这两个注解配置后Spring会初始化WebMvcConfigurationSupport对象,该对象提供MVC Java配置主要类。

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport

WebMvcConfigurationSupport对象中其他属性和方法暂且不去关注,我们直接关注如下方法:

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters
/**
	 * Adds a set of default HttpMessageConverter instances to the given list.
	 * Subclasses can call this method from {@link #configureMessageConverters}.
	 * @param messageConverters the list to add the default message converters to
	 */
	protected final void addDefaultHttpMessageConverters(List> messageConverters) {
		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(new StringHttpMessageConverter());
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new ResourceRegionHttpMessageConverter());
		try {
			messageConverters.add(new SourceHttpMessageConverter<>());
		}
		catch (Throwable ex) {
			// Ignore when no TransformerFactory implementation is available...
		}
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
		else if (jaxb2Present) {
			messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
		else if (gsonPresent) {
			messageConverters.add(new GsonHttpMessageConverter());
		}
		else if (jsonbPresent) {
			messageConverters.add(new JsonbHttpMessageConverter());
		}

		if (jackson2SmilePresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
		}
		if (jackson2CborPresent) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
			if (this.applicationContext != null) {
				builder.applicationContext(this.applicationContext);
			}
			messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
		}
	}

从源码中可以看到默认直接添加了

ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
ResourceRegionHttpMessageConverter
SourceHttpMessageConverter
AllEncompassingFormHttpMessageConverter

其他的添加都是有条件的,查看对应条件的初始化方法:

static {
		ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}

org.springframework.util.ClassUtils#isPresent方法就是检查一个Class是否存在。有点类是使用如下代码进行检查,只不过Spring封装得更加完善。

    @Test
    public void testIsPresent() {
        System.out.println(classIsPresent("com.fasterxml.jackson.databind.ObjectMapper"));
    }

    private boolean classIsPresent(String classname) {
        try {
            Class.forName(classname);
            return true;
        } catch (ClassNotFoundException e) {
            // ignore

        }
        return false;
    }

可以看到Spring默认有支持jackson、Gson、Jsonb的支持。到此,引起异常根源的原因找到了,那现在解决方法就比较多了。

方式一:是引入jackson的核心依赖

    
      com.fasterxml.jackson.core
      jackson-core
      2.11.2
    
    
      com.fasterxml.jackson.core
      jackson-annotations
      2.11.2
    
    
      com.fasterxml.jackson.core
      jackson-databind
      2.11.2
    

方式二(推荐使用):引入jackson-dataformat-cbor和jackson-dataformat-xml依赖,这两个依赖也会包含方式一的三个jackson的核心依赖

        
            com.fasterxml.jackson.dataformat
            jackson-dataformat-cbor
            2.11.2
        
        
            com.fasterxml.jackson.dataformat
            jackson-dataformat-xml
            2.11.2
        

方式三:引入Gson

        
            com.google.code.gson
            gson
            2.8.5
        

方式四:引入Jsonb

        
            io.quarkus
            quarkus-jsonb
            1.8.1.Final
        

以上内容是个人在学习Spring MVC基于Java配置(注解配置)的开发工程中遇到该问题的解决流程和自己的一些对于Spring源码的理解,若有问题或者错误的地方,欢迎指正。

 

 

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