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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 05:26:52  来源:igfitidea点击:

How to change the content type in exception handler

springspring-mvcmime-types

提问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:

这个处理程序允许返回字符串被客户端MappingHymanson2HttpMessageConverterStringHttpMessageConverter依赖传入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@RequestMappinggetMetaInformation会给你想要的结果。

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, while MappingHymanson2HttpMessageConverteris bound to application/jsononly.
  • When @RequestMappingis providing produces = ..., this value is stored in HttpServletRequest(see RequestMappingInfoHandlerMapping.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 only MappingHymanson2HttpMessageConvertercan 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 将再次适用。