How to change the content type in exception handler

前端 未结 2 1899
渐次进展
渐次进展 2020-12-13 18:19

Suppose I have a controller that serves GET request and returns bean to be serialized to JSON and also provides an exception handler for IllegalArgumentEx

相关标签:
2条回答
  • 2020-12-13 19:07

    There are several aspects relating to the problem:

    • StringHttpMessageConverter adds catch-all mime type */* to the list of supported media types, while MappingJackson2HttpMessageConverter is bound to application/json only.
    • When @RequestMapping is 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.

    The solution in simple case would be to put StringHttpMessageConverter first in the list:

    <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>
    

    and also remove produces from @RequestMapping annotation:

    @RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET)
    @ResponseBody
    public MetaInformation getMetaInformation(@PathVariable int itemId) {
        return myService.getMetaInformation(itemId);
    }
    

    Now:

    • StringHttpMessageConverter will discard all types, which only MappingJackson2HttpMessageConverter can handle (MetaInformation, java.util.Collection, etc) allowing them to be passed further.
    • In case of exception in scenario (5, 6) StringHttpMessageConverter will take the precedence.

    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 ObjectToStringHttpMessageConverter fails. To solve the problem we keep produces for @RequestMapping annotation where JSON convertor should be used, but to force correct content type for exception handler, we will erase HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from 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.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>
    

    Now scenarios (1,2,3,4) are handled correctly because of content-type negotiation, and scenarios (5,6) are processed in exception handler.

    Alternatively one can replace collection return type with arrays, then solution #1 is applicable again:

    @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:

    Type returnValueType = returnType.getGenericParameterType();

    and HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) should be changed to:

    canWrite(Type returnType, MediaType mediaType)

    or (in case it is too limiting potential class-based convertors) to

    canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)

    Then parametrized types could be handled correctly and solution #1 would be applicable again.

    0 讨论(0)
  • 2020-12-13 19:18

    I think removing the produces = MediaType.APPLICATION_JSON_VALUE from @RequestMapping of the getMetaInformation will give you the desired result.

    The response-type will be negotiated according to the content-type value in the Accept header.


    edit

    As this does not cover scenario 3,4 here is a solution working with ResponseEntity.class directly:

    @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);
    }
    
    0 讨论(0)
提交回复
热议问题