如何更改异常处理程序中的内容类型

时间:2012-10-19 15:18:13

标签: spring spring-mvc mime-types

假设我有一个服务器提供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";
}

此处理程序允许返回字符串由MappingJackson2HttpMessageConverterStringHttpMessageConverter序列化,具体取决于客户端传递的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          |
+----+---------------------+-----------------------+------------------+-------------------------------------+

2 个答案:

答案 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才能处理(MetaInformationjava.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将再次适用。