今天天气不错 正在听着相声测接口,结果忽然出现了以下异常:
The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.
纳尼!!!这异常 翻译过来的大概意思就是请求与相应的头信息不匹配
具体返回信息如图:
我寻摸着不应该啊?其他接口调的好好的怎么回事呢?本着偷懒的精神打开了百度 输入关键词 :spring mvc HTTP Status 406 搜出来的大部分文章都是让我去配置 xml里 对应的
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
增加对应的解析器,于是乎我就去检查我的配置文件:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />
</bean>
</property>
</bean>
</property>
</bean>
</list>
</property>
<property name="webBindingInitializer" ref="webBindingInitializer"/>
</bean>
我的配置文件好好的啊根本不用增加任何配置,于是我决定放弃搜索,直接开始走源码跟踪,首先我分析我这个返回值增加了@ResponseBody注解,于是我就搜索Spring 控制返回值的源码类,我首先找到的是:RequestMappingHandlerAdapter,
毕竟这个类是处理我们每个请求的处理器,然后我看到了处理返回值的处理器:
于是我又跟到了HandlerMethodReturnValueHandlerComposite类里,我可以肯定我找到了搜索的源头,于是我点了断点开始对代码进行调试,当代码进入HandlerMethodReturnValueHandlerComposite.handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)的时候我们可以看到一共有多少种返回值处理器:
找到了我们对应的返回值处理器,接着往下走请求到了:RequestResponseBodyMethodProcessor中,
我们顺着代码接着往下走,最终请求到了writeWithMessageConverters()这个方法,看解释知道这个方法就是根据我们的返回值类型去匹配对应的转换器,这个方法很长我们分析一下:
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
#获取返回值类型
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
HttpServletRequest servletRequest = inputMessage.getServletRequest();
#获取请求的MediaType
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
#根据我们@RequestMapping注解的配置获取可用的返回值MediaType
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
#这里是重点了 这里就是判断我们的返回值类型是否可以用我们想返回的类型来转换
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if (returnValue != null) {
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
}
return;
}
}
}
if (returnValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
源码很简单 我们标记了一下关键的地方,我们看下图中箭头指向的方法,这个方法就是判断我们的返回值类型是否可以用我们执行的MessageConverter来转换返回值,我们应该马上就接近真相了,接着我们继续跟进去寻找真相
我们根据上面的代码看到具体处理的解析器是MappingJackson2HttpMessageConverter 它又继承了AbstractJackson2HttpMessageConverter,而canWrite()方法就是其父类AbstractJackson2HttpMessageConverter的方法,我们进入方法只需要关注如图两个方法
我们可以看到他会使用ObjectMapper判断返回的类是否可序列化,那么有没有可能是我们的bean这层校验都没通过呢?我们接着往下跟,这一层代码层次比较多了 我直接穿到对底层序列化的那一段了分析我的问题了,想了解的小伙伴自己可以一步一步的看看源码,中间省略了一大堆代码的跟踪 最终是跳到了BeanSerializerFactory._createSerializer2()方法中,再执行过程中发现了个异常:
于是乎我明白哪里出现了问题,我赶紧根据异常去查询返回的bean,终于找到原因了,是因为我的bean里有个字段是Boolean类型的,不知道是谁写的时候生成了两个方法isXX的方法和getXX,在序列化的时候它就不知道要用哪个了 就告诉你:喂 老兄你提供的两个方法是不是冲突了你这让我用哪个啊? 所以代码罢工了不对你的结果进行序列化了并且告诉Spring说这个老兄给的代码不能序列化,所以Spring最终抛出来一个HttpMediaTypeNotAcceptableException ,而为什么会出现上述的转换类型异常呢?如果你获取了Response的Context-Type你一定会发现是text/html,而这又是为什么呢?那是因为虽然Spring处理完了异常也抛出了,但是我们的web容器还有一系列的处理,我们继续跟踪代码,最后我们会发现tomcat的一系列执行过程(我这里的web容器用的是tomcat)最后我发现了原来返回值的Content-Type其实是web容器转换的
到这里我们基本搞清楚为什么会出现这个问题了,不管怎样又把源码复习了一遍 起码把@ResponseBody处理流程给弄清楚了,我们由点及面,可以继续把参数处理的过程也快速的过一遍这样就把整个Spring 处理Request的流程又巩固一遍。
如果你在开发中出现了搜索引擎解决不了的问题,那么请耐心去读一下源码,我们要时刻抱着知其然 知其所以然的态度去对待每一次错误,让每一次错误都能使我们进步 !