@PostMapping(value = "/interface1")
@ResponseBody
public BaseResponse interface1(@RequestBody BaseReqDto reqDto) {
//....
return BaseResponse.ok(null);
}
@PostMapping(value = "/interface2", produces = "application/json")
@ResponseBody
public BaseResponse interface2(@RequestBody BaseReqDto reqDto) {
//....
return BaseResponse.ok(null);
}
@PostMapping(value = "/interface3.json")
@ResponseBody
public BaseResponse interface3(@RequestBody BaseReqDto reqDto) {
//....
return BaseResponse.ok(null);
}
postman请求接口,interface1返回为XML格式,但是APP端请求返回值为json格式?
本文实际上是探究Spring MVC如何处理@ResponseBody的,
代码interface2、interface3对比使用。
涉及到的核心类:DispatcherServlet、AbstractHandlerMethodAdapter、RequestMappingHandlerAdapter、HandlerMethodReturnValueHandlerComposite、RequestResponseBodyMethodProcessor、AbstractMessageConverterMethodProcessor、WebMvcConfigurationSupport
Spring版本:5.1.2
环境:大量包依赖
在doDispatch的前后查看response的响应头,看是在哪一步设置的值:
=== MimeHeaders ===
Content-Type = application/json;charset=UTF-8
Transfer-Encoding = chunked
Date = Sun, 21 Jun 2020 14:11:09 GMT
可以定位到handle方法;
在RequestMappingHandlerAdapter中可以看到有对响应值处理的是invocableMethod的invokeAndHandle方法,并且该方法也是实际调用业务方法的入口;
进入这个方法,可以得到业务实际返回的类型,明显的在handleReturnValue方法中处理返回值。
在handleReturnValue中的selectHandler方法值得去跟一遍,看Spring MVC有那些处理返回值的Handler,在上面的用法中,Spring MVC使用的是RequestResponseBodyMethodProcessor
,判断依据就是是否有注解@ResponseBody:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
可以看到Spring支持多种返回值处理器,包括异步返回的,Spring 扩展性真强。
实际上对返回值处理的是AbstractMessageConverterMethodProcessor类中的writeWithMessageConverters方法;
核心代码如下:
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); //1.
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); //2.
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
//3.
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
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<?> 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);
if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);//4.
}
}
return;
}
}
}
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);//5.
}
a: 不设置produces
这个与引入的jar包有关,可以在WebMvcConfigurationSupport中找到支持的类型
b: 设置了produces = “application/json”,如interface2
看一下WebMvcConfigurationSupport的配置:
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);
}
protected final void addDefaultHttpMessageConverters(List> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(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()));
}
}
项目中引入了jackson-dataformat-xml,所以激活了xml的选项,这里有两种修改方式,但是都不推荐:
c: 排除jar包,但是有人引入可能有人用,或者引入的地方过多,或者后面可能有人会引入,不要挖坑
d: 指定默认的ContentType,即调整一下顺序:
@Configuration
public class WebInterceptorAdapter implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_XML);
}
}
但是这种会打包了Spring Boot的自动配置能力