How to change the content type in exception handler

前端 未结 2 1898
渐次进展
渐次进展 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:

    
        
            
                
                    
                        
                    
                
            
            
        
    
    

    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 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 getMetaInformations() {
        return myService.getMetaInformations();
    }
    

    Context stays the same as it was originally:

    
        
            
            
                
                    
                
                
                    
                        
                    
                
            
        
    
    

    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.

提交回复
热议问题