1. 问题描述
一个简单的spring mvc样例,spring-webmvc框架版本为5.1.5.RELEASE,里面定义了两个RestController接口方法,
@RestController
@RequestMapping(value = "/api", produces = "application/json; charset=utf-8")
public class SimpleController {
@RequestMapping(value = "test", method = RequestMethod.GET)
public String test() {
return "hello,world";
}
@RequestMapping(value = "test2", method = RequestMethod.GET)
public Boolean test2() {
return Boolean.TRUE;
}
}
通过http客户端工具调用/api/test接口能够正常返回信息,但是调用/api/test2时,报如下错误信息,
<body><h1>HTTP Status 406 – Not Acceptable</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Description</b> The target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation.</p><hr class="line" /><h3>Apache Tomcat/8.5.37</h3></body>
后端日志报如下HttpMediaTypeNotAcceptableException异常信息,
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
2. 问题调查
问题奇怪在于,接口api/test能正常返回信息,但接口api/test2报错。比较下两个接口,都差不多,区别在于:前者返回String,后者返回Boolean对象。
调试跟踪api/test2的接口,发现异常HttpMediaTypeNotAcceptableException在如下位置被抛出,
/** 调用栈
* DispatcherServlet.doDispatch()
* AbstractHandlerMethodAdapter.handle()
* RequestMappingHandlerAdapter.invokeHandlerMethod()
* ServletInvocableHandlerMethod.invokeAndHandle(webRequest, mavContainer, providedArgs)
* this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
* AbstractMessageConverterMethodProcessor.handleReturnValue()
* this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
*
* 到达类AbstractMessageConverterMethodProcessor中的如下方法,
* 注:如下为反编译代码,非源码,仅供问题调查
*/
protected <T> void writeWithMessageConverters(...){
...
HttpMessageConverter converter;
GenericHttpMessageConverter genericConverter;
label138: {
if(selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
var21 = this.messageConverters.iterator();
while(var21.hasNext()) {
converter = (HttpMessageConverter)var21.next();
genericConverter = converter instanceof GenericHttpMessageConverter?(GenericHttpMessageConverter)converter:null;
if(genericConverter != null) {
if(((GenericHttpMessageConverter)converter).canWrite((Type)declaredType, valueType, selectedMediaType)) {
break label138;
}
} else if(converter.canWrite(valueType, selectedMediaType)) {
break label138;
}
}
}
if(outputValue != null) {
// 异常抛出点
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
return;
}
...
}
查看上传代码,可以了解异常抛出的原因,是因为没有找到合适HttpMessageConverter,无法转换返回的消息。
调试查看this.messageConverters对象,发现有converters列表如下,
this.messageConverters ( ArrayList size = 7)
[0] ByteArrayHttpMessageConverter
[1] StringHttpMessageConverter
[2] ResourceHttpMessageConverter
[3] ResourceRegionHttpMessageConverter
[4] SourceHttpMessageConverter
[5] AllEncompassingFormHttpMessageConverter
[6] Jaxb2CollectionHttpMessageConverter
这个列表没有发现我们熟悉的Json Converter,于是接下来开始追查messageConverters的列表初始化,为什么没有Json converter?
查看spring mvc源码可以看到如下的convert初始化过程,
1. 在类RequestMappingHandlerAdapter中创建this.messageConverters列表对象,并加载各个缺省convert。
2. 其中AllEncompassingFormHttpMessageConverter被创建,在这个类中,其会检查当前JVM中是否加载了Jackson/Gson相关类,若有,则加载json converter。
public class RequestMappingHandlerAdapter {
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false);
this.messageConverters = new ArrayList(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
}
public class AllEncompassingFormHttpMessageConverter {
private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", ...);
private static final boolean gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", ...);
public AllEncompassingFormHttpMessageConverter() {
...
if(jackson2Present) {
this.addPartConverter(new MappingJackson2HttpMessageConverter());
} else if(gsonPresent) {
this.addPartConverter(new GsonHttpMessageConverter());
} else if(jsonbPresent) {
this.addPartConverter(new JsonbHttpMessageConverter());
}
...
}
}
由此可以看到,缺少json converter的原因就是缺少相应的Jackson/Gson类库,而默认的converter无法对Boolean对象转换,从而产生HttpMediaTypeNotAcceptableException异常,接而导致HTTP 406的返回消息。
3. 问题解决
在项目中添加如下依赖,加载MappingJackson2HttpMessageConverter到this.messageConverters列表中,问题得到解决。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${fasterxml.version}</version>
</dependency>