spring 如何更改异常处理程序中的内容类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12977368/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to change the content type in exception handler
提问by dma_k
Suppose I have a controller that serves GETrequest and returns bean to be serialized to JSON and also provides an exception handler for IllegalArgumentExceptionthat can be raised in service:
假设我有一个控制器,它为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);
}
Message convertors are:
消息转换器是:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
Now when I request the given URL in browser I see the correct JSON reply. However if exception is raised, the stringified exception is converted into JSON as well, but I would love it to be processed by StringHttpMessageConverter(resulting text/plainmime type). How can I go it?
现在,当我在浏览器中请求给定的 URL 时,我会看到正确的 JSON 回复。但是,如果引发异常,字符串化异常也会转换为 JSON,但我希望它由StringHttpMessageConverter(生成的text/plainmime 类型)处理。我怎么去呢?
To make the picture more complete (and complicated), suppose I also have the following handler:
为了使图片更完整(和复杂),假设我还有以下处理程序:
@RequestMapping(value = "/version", method = RequestMethod.GET)
@ResponseBody
public String getApplicationVersion() {
return "1.0.12";
}
This handler allows the return string to be serialized by both MappingHymanson2HttpMessageConverterand StringHttpMessageConverterdepending in passed Accept-typeby the client. The return types and values should be as following:
这个处理程序允许返回字符串被客户端MappingHymanson2HttpMessageConverter和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 | MappingHymanson2HttpMessageConverter | | 3. | /meta/1 | text/html; */* | application/json | MappingHymanson2HttpMessageConverter | | 4. | /meta/1 | application/json; */* | application/json | MappingHymanson2HttpMessageConverter | | 5. | /meta/0 (exception) | text/html; */* | text/plain | StringHttpMessageConverter | | 6. | /meta/0 (exception) | application/json; */* | text/plain | StringHttpMessageConverter | +----+---------------------+-----------------------+------------------+-------------------------------------+
采纳答案by oehmiche
I think removing the produces = MediaType.APPLICATION_JSON_VALUEfrom @RequestMappingof the getMetaInformationwill give you the desired result.
我想移除produces = MediaType.APPLICATION_JSON_VALUE从@RequestMapping的getMetaInformation会给你想要的结果。
The response-type will be negotiated according to the content-type value in the Accept header.
响应类型将根据 Accept 标头中的内容类型值进行协商。
edit
编辑
As this does not cover scenario 3,4 here is a solution working with ResponseEntity.classdirectly:
由于这不包括场景 3,4,这里是一个ResponseEntity.class直接使用的解决方案:
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleIllegalArgumentException(Exception ex) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<String>(ex.getMessage(), headers, HttpStatus.BAD_REQUEST);
}
回答by dma_k
There are several aspects relating to the problem:
这个问题有几个方面:
StringHttpMessageConverteradds catch-all mime type*/*to the list of supported media types, whileMappingHymanson2HttpMessageConverteris bound toapplication/jsononly.- When
@RequestMappingis providingproduces = ..., this value is stored inHttpServletRequest(seeRequestMappingInfoHandlerMapping.handleMatch()) and when the error handler is called, this mime type is automatically inherited and used.
StringHttpMessageConverter将所有 MIME 类型添加*/*到支持的媒体类型列表中,而MappingHymanson2HttpMessageConverter仅绑定到application/json。- 当
@RequestMapping提供 时produces = ...,这个值存储在HttpServletRequest(见RequestMappingInfoHandlerMapping.handleMatch())中,当错误处理程序被调用时,这个 mime 类型被自动继承和使用。
The solution in simple case would be to put StringHttpMessageConverterfirst in the list:
简单情况下的解决方案是将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.MappingHymanson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
and also remove producesfrom @RequestMappingannotation:
并produces从@RequestMapping注释中删除:
@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
return myService.getMetaInformation(itemId);
}
Now:
现在:
StringHttpMessageConverterwill discard all types, which onlyMappingHymanson2HttpMessageConvertercan handle (MetaInformation,java.util.Collection, etc) allowing them to be passed further.- In case of exception in scenario (5, 6)
StringHttpMessageConverterwill take the precedence.
StringHttpMessageConverter将丢弃所有类型,这些类型只能MappingHymanson2HttpMessageConverter处理 (MetaInformation,java.util.Collection, 等) 允许它们进一步传递。- 如果场景 (5, 6) 中出现异常,
StringHttpMessageConverter则优先考虑。
So far so good, but unfortunately things get more complicated with ObjectToStringHttpMessageConverter. For handler return type java.util.Collection<MetaInformation>this message convertor will report that it can convert this type to java.lang.String. The limitation comes from the fact that collection element types are erased and AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)method gets java.util.Collection<?>class for check, however when it comes to conversion step ObjectToStringHttpMessageConverterfails. To solve the problem we keep producesfor @RequestMappingannotation where JSON convertor should be used, but to force correct content type for exception handler, we will erase HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTEattribute from HttpServletRequest:
到目前为止一切顺利,但不幸的是,使用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();
}
Context stays the same as it was originally:
上下文保持原样:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter" />
<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>
Now scenarios (1,2,3,4) are handled correctly because of content-type negotiation, and scenarios (5,6) are processed in exception handler.
现在场景 (1,2,3,4) 由于内容类型协商被正确处理,场景 (5,6) 在异常处理程序中处理。
Alternatively one can replace collection return type with arrays, then solution #1 is applicable again:
或者,可以用数组替换集合返回类型,然后解决方案 #1 再次适用:
@RequestMapping(value = "/meta", method = RequestMethod.GET)
@ResponseBody
public MetaInformation[] getMetaInformations() {
return myService.getMetaInformations().toArray();
}
For discussion:
供讨论:
I think that AbstractMessageConverterMethodProcessor.writeWithMessageConverters()should not inherit class from value, but rather from method signature:
我认为不AbstractMessageConverterMethodProcessor.writeWithMessageConverters()应该从值继承类,而是从方法签名:
Type returnValueType = returnType.getGenericParameterType();
Type returnValueType = returnType.getGenericParameterType();
and HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)should be changed to:
并且HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)应该改为:
canWrite(Type returnType, MediaType mediaType)
canWrite(Type returnType, MediaType mediaType)
or (in case it is too limiting potential class-based convertors) to
或者(如果它过于限制潜在的基于类的转换器)到
canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)
canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)
Then parametrized types could be handled correctly and solution #1 would be applicable again.
然后可以正确处理参数化类型,并且解决方案 #1 将再次适用。

