33

假设我有一个控制器,它提供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/plainmime 类型)处理。我怎么去呢?

为了使图片更完整(和复杂),假设我还有以下处理程序:

@RequestMapping(value = "/version", method = RequestMethod.GET)
@ResponseBody
public String getApplicationVersion() {
    return "1.0.12";
}

此处理程序允许返回字符串由两者序列化,MappingJackson2HttpMessageConverterStringHttpMessageConverter取决于Accept-type客户端传递的内容。返回类型和值应如下所示:

+----+----------+---------- -+------------------+-------------- --------+
| 神经网络 | 网址 | 接受型 | 内容类型 | 消息转换器 |
| | | 请求头 | 响应头 | |
+----+----------+---------- -+------------------+-------------- --------+
| 1. | /版本 | 文本/html;*/* | 文本/纯文本 | StringHttpMessage 转换器 |
| 2. | /版本 | 应用程序/json;*/* | 应用程序/json | MappingJackson2HttpMessageConverter |
| 3. | /元/1 | 文本/html;*/* | 应用程序/json | MappingJackson2HttpMessageConverter |
| 4. | /元/1 | 应用程序/json;*/* | 应用程序/json | MappingJackson2HttpMessageConverter |
| 5. | /meta/0 (例外) | 文本/html;*/* | 文本/纯文本 | StringHttpMessage 转换器 |
| 6. | /meta/0 (例外) | 应用程序/json;*/* | 文本/纯文本 | StringHttpMessage 转换器 |
+----+----------+---------- -+------------------+-------------- --------+
4

2 回答 2

30

我认为删除produces = MediaType.APPLICATION_JSON_VALUEfrom会给你想要的结果@RequestMappinggetMetaInformation

response-type 将根据 Accept 头中的 content-type 值进行协商。


编辑

由于这不涵盖场景 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);
}
于 2012-10-19T16:53:57.193 回答
15

有几个方面与问题有关:

  • 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, 等) 允许它们被进一步传递。
  • 情况(5、6)中的异常情况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 将再次适用。

于 2012-10-19T17:39:21.463 回答