先说结论
- @requestmapping不指定produces注解值的情况下,@ResponseBody不一定返回json,而是根据request请求头的accept类型,根据权重返回
- @requestmapping指定produces的情况下,reponse会设置reponse body的header为对应类型,不在根据请求接受的类型返回数据
csdn :@ResponseBody并不是以json返回。不加@ResponseBody,是将方法返回的值作为视图名称,并自动匹配视图去显示,而加上@ResponseBody就仅仅是将方法返回值当作内容直接返回到客户端,并且会自适应响应头的content-type,返回的字符串符合json,那么content-type就是application/json,如果是普通字符串,就是text/plain,但是加上注解属性produces=application/json,那么不管内容是什么格式,响应头的content-type就一直是application/json,不再去做自适应,至于内容是不是json都不重要了
背景
- @ResponseBody 默认情况返回的数据格式是什么?所谓默认情况 后台接口不指定 produces MediaType
@Controller
public class DemoController {
@ResponseBody
@GetMapping(value = "/demo")
public DemoVO demo() {
return new DemoVO("lengleng", "123456");
}
}
- 使用百度搜索 @ResponseBody 排名第一的答案, @ResponseBody 的作用其实是将 java 对象转为 json 格式的数据。

正确答案
我们先来公布正确的答案。
@ResponseBody 的输出格式,默认情况取决于客户端的 Accept 请求头。


源码剖析
- RequestResponseBodyMethodProcessor
public class RequestResponseBodyMethodProcessor {
// 处理 ResponseBody 标注的方法
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
// 处理返回值
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 处理返回值
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}
- writeWithMessageConverters
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) {
HttpServletRequest request = inputMessage.getServletRequest();
// 获取请求头中的目标资源类型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 获取接口指定支持的资源类型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
// 获取能够输出资源类型
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
/// 排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
// 判断资源类型是否是具体的类型,而不是带通配符 * 这种
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
selectedMediaType = selectedMediaType.removeQualityValue();
// 查找支持选中资源类型的 HttpMessageConverter,输出body
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
return;
}
}
}
为什么我要去研究这个问题
- 当升级至
spring cloud alibaba 2.2.1时, sentinel 模块 引入以下依赖

- 当依赖中出现 dataformat jar 时候, RestTemplate ,会在默认 Accept 请求头增加
application/xml | text/xml | application/*+xml

public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, new MediaType("application", "xml", StandardCharsets.UTF_8),
new MediaType("text", "xml", StandardCharsets.UTF_8),
new MediaType("application", "*+xml", StandardCharsets.UTF_8));
Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required");
}
- 当我们使用 RestTemplate 调用接口时候,若不指定 Accept 会返回 XML ,导致不能平滑升级
相同的问题,参考:
FeignClient: no suitable HttpMessageConverter for application/octet-stream 问题解决
https://www.jianshu.com/p/54b45e4fdcf3
因为feignclient调用时,未指定accept-type的类型,导致接口返回的数据格式不是json,而是application/octet-stream类型,也就是未知二进制类型,导致feignclient无法解析消息体。
HTTP报文头Accept和Content-Type总结
1.Accept属于请求头, Content-Type属于实体头。
Http报头分为通用报头,请求报头,响应报头和实体报头。
请求方的http报头结构:通用报头|请求报头|实体报头
响应方的http报头结构:通用报头|响应报头|实体报头
2.Accept代表发送端(客户端)希望接受的数据类型。
比如:Accept:text/xml(application/json);
代表客户端希望接受的数据类型是xml(json )类型
Content-Type代表发送端(客户端|服务器)发送的实体数据的数据类型。
比如:Content-Type:text/html(application/json) ;
代表发送端发送的数据格式是html(json)。
二者合起来,
Accept:text/xml;
Content-Type:text/html
即代表希望接受的数据类型是xml格式,本次请求发送的数据的数据格式是html。
如果accept指定的类型和response返回的类型不一致,会出现406,not acceptable错误,对应到java spring工程,就是
HttpHeaders headers = new HttpHeaders();
headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE.toString());
如果head头里加了accept,那么@RequestMapping里的produces = "*/*"或produces = "application/json"
Conteny-Type:内容类型,即请求/响应的内容区数据的媒体类型
Accept:用来指定什么媒体类型的响应是可接受的,即告诉服务器我需要什么媒体类型的数据,此时服务器应该根据Accept请求头生产指定媒体类型的数据。
问题:
服务器端可以通过指定【headers = “Content-Type=application/json”】来声明可处理(可消费)的媒体类型,即只消费Content-Type指定的请求内容体数据;
客户端如何告诉服务器端它只消费什么媒体类型的数据呢?即客户端接受(需要)什么类型的数据呢?服务器应该生产什么类型的数据?此时我们可以请求的Accept请求头来实现这个功能。
@RequestMapping(value = "/response/ContentType", headers = "Accept=application/json")
public void response2(HttpServletResponse response) throws IOException {
//表示响应的内容区数据的媒体类型为json格式,且编码为utf-8(客户端应该以utf-8解码)
response.setContentType("application/json;charset=utf-8");
//写出响应体内容
String jsonData = "{\"username\":\"zhang\", \"password\":\"123\"}";
response.getWriter().write(jsonData);
}
@Controller
@RequestMapping(value = "/users", method = RequestMethod.POST, consumes="application/json", produces="application/json")
@ResponseBody
public List<User> addUser(@RequestBody User userl) {
// implementation omitted
return List<User> users;
}
上面两个例子都表示了request请求中Accept头中包含了"application/json"的请求,同时暗示了返回的内容类型为application/json:
produces标识:produces="application/json"
headers = "Accept=application/json"
其中request Content-Type为“application/json”类型的请求.
当你有如下Accept头,将遵守如下规则进行应用:
Accept:text/html,application/xml,application/json
将按照如下顺序进行produces的匹配 ①text/html ②application/xml ③application/json
Accept:application/xml;q=0.5,application/json;q=0.9,text/html
将按照如下顺序进行produces的匹配 ①text/html ②application/json ③application/xml
参数为媒体类型的质量因子,越大则优先权越高(从0到1)
Accept:*/*,text/*,text/html
将按照如下顺序进行produces的匹配 ①text/html ②text/* ③/
即匹配规则为:最明确的优先匹配。