假设我有一个服务器提供GET
请求并返回要序列化为JSON的bean,并且还提供了可以在服务中引发的IllegalArgumentException
异常处理程序:
@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
return myService.getMetaInformation(itemId);
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleIllegalArgumentException(IllegalArgumentException ex) {
return ExceptionUtils.getStackTrace(ex);
}
消息转换器是:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
现在,当我在浏览器中请求给定的URL时,我看到了正确的JSON回复。但是,如果引发异常,则字符串化的异常也会转换为JSON,但我希望它由StringHttpMessageConverter
处理(生成text/plain
mime类型)。我该怎么办?
为了使图片更完整(和复杂),假设我还有以下处理程序:
@RequestMapping(value = "/version", method = RequestMethod.GET)
@ResponseBody
public String getApplicationVersion() {
return "1.0.12";
}
此处理程序允许返回字符串由MappingJackson2HttpMessageConverter
和StringHttpMessageConverter
序列化,具体取决于客户端传递的Accept-type
。返回类型和值应如下所示:
+----+---------------------+-----------------------+------------------+-------------------------------------+ | NN | URL | Accept-type | Content-type | Message converter | | | | request header | response header | | +----+---------------------+-----------------------+------------------+-------------------------------------+ | 1. | /version | text/html; */* | text/plain | StringHttpMessageConverter | | 2. | /version | application/json; */* | application/json | MappingJackson2HttpMessageConverter | | 3. | /meta/1 | text/html; */* | application/json | MappingJackson2HttpMessageConverter | | 4. | /meta/1 | application/json; */* | application/json | MappingJackson2HttpMessageConverter | | 5. | /meta/0 (exception) | text/html; */* | text/plain | StringHttpMessageConverter | | 6. | /meta/0 (exception) | application/json; */* | text/plain | StringHttpMessageConverter | +----+---------------------+-----------------------+------------------+-------------------------------------+
答案 0 :(得分:20)
我认为从produces = MediaType.APPLICATION_JSON_VALUE
的{{1}}中删除@RequestMapping
可以获得所需的结果。
将根据Accept标头中的content-type值协商响应类型。
修改的
由于这不包括方案3,4,这里是直接使用getMetaInformation
的解决方案:
ResponseEntity.class
答案 1 :(得分:10)
与此问题有关的几个方面:
StringHttpMessageConverter
将包含所有mime类型*/*
添加到支持的媒体类型列表中,而MappingJackson2HttpMessageConverter
仅绑定到application/json
。@RequestMapping
提供produces = ...
时,此值存储在HttpServletRequest
中(请参阅RequestMappingInfoHandlerMapping.handleMatch()
),当调用错误处理程序时,此mime类型会自动继承,使用。简单案例的解决方案是将StringHttpMessageConverter
放在列表的第一位:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<array>
<util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
</array>
</property>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
并从produces
注释中移除@RequestMapping
:
@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
return myService.getMetaInformation(itemId);
}
现在:
StringHttpMessageConverter
将丢弃所有类型,只有MappingJackson2HttpMessageConverter
才能处理(MetaInformation
,java.util.Collection
等),允许它们进一步传递。StringHttpMessageConverter
将优先。到目前为止一直很好,但遗憾的是ObjectToStringHttpMessageConverter
事情变得更加复杂。对于处理程序返回类型java.util.Collection<MetaInformation>
,此消息转换器将报告它可以将此类型转换为java.lang.String
。该限制来自以下事实:集合元素类型被删除,AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)
方法获取java.util.Collection<?>
类进行检查,但是当涉及到转换步骤ObjectToStringHttpMessageConverter
失败时。为了解决这个问题,我们保留produces
@RequestMapping
注释应该使用JSON转换器,但要强制使用异常处理程序的正确内容类型,我们将从HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
中删除HttpServletRequest
属性:
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return ExceptionUtils.getStackTrace(ex);
}
@RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Collection<MetaInformation> getMetaInformations() {
return myService.getMetaInformations();
}
上下文与原来保持一致:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
<bean class="org.springframework.http.converter.ObjectToStringHttpMessageConverter">
<property name="conversionService">
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" />
</property>
<property name="supportedMediaTypes">
<array>
<util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
现在由于内容类型协商而正确处理场景(1,2,3,4),并且在异常处理程序中处理场景(5,6)。
或者,可以用数组替换集合返回类型,然后解决方案#1再次适用:
@RequestMapping(value = "/meta", method = RequestMethod.GET)
@ResponseBody
public MetaInformation[] getMetaInformations() {
return myService.getMetaInformations().toArray();
}
讨论:
我认为AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
不应该从值继承类,而应该从方法签名继承:
Type returnValueType = returnType.getGenericParameterType();
和HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)
应更改为:
canWrite(Type returnType, MediaType mediaType)
或(如果它过于限制潜在的基于类的转换器)
canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)
然后可以正确处理参数化类型,并且解决方案#1将再次适用。