restcontrolleradvice manejo excepciones example error controlleradvice spring spring-mvc mime-types

manejo - spring response exception handler



Cómo cambiar el tipo de contenido en el manejador de excepciones (2)

Creo que eliminar el produces = MediaType.APPLICATION_JSON_VALUE de @RequestMapping de getMetaInformation le dará el resultado deseado.

El tipo de respuesta se negociará de acuerdo con el valor del tipo de contenido en el encabezado Aceptar.

editar

Como esto no cubre el escenario 3,4, aquí hay una solución que trabaja directamente con 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); }

Supongamos que tengo un controlador que sirve la solicitud GET y devuelve bean para ser serializado a JSON y también proporciona un controlador de excepción para IllegalArgumentException que se puede IllegalArgumentException en el servicio:

@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); }

Los convertidores de mensajes son:

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

Ahora cuando solicito la URL dada en el navegador, veo la respuesta correcta de JSON. Sin embargo, si se produce una excepción, la excepción encerrada también se convierte en JSON, pero me gustaría que fuera procesada por StringHttpMessageConverter ( text/plain resultante text/plain tipo de mime sin text/plain ). ¿Cómo puedo ir?

Para que la imagen sea más completa (y complicada), supongamos que también tengo el siguiente controlador:

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

Este manejador permite que la cadena de retorno sea serializada tanto por MappingJackson2HttpMessageConverter como por StringHttpMessageConverter dependiendo Accept-type StringHttpMessageConverter por el cliente. Los tipos y valores de devolución deberían ser los siguientes:

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


Hay varios aspectos relacionados con el problema:

  • StringHttpMessageConverter agrega catch-all mime type */* a la lista de tipos de medios admitidos, mientras que MappingJackson2HttpMessageConverter está vinculado a application/json .
  • Cuando @RequestMapping proporciona produces = ... , este valor se almacena en HttpServletRequest (ver RequestMappingInfoHandlerMapping.handleMatch() ) y cuando se llama al manejador de errores, este tipo de mime se hereda y usa automáticamente.

La solución en caso simple sería poner primero a StringHttpMessageConverter en la lista:

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

y también eliminar produces de la anotación @RequestMapping :

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

Ahora:

  • StringHttpMessageConverter descartará todos los tipos, que solo MappingJackson2HttpMessageConverter puede manejar ( MetaInformation , java.util.Collection , etc.) lo que les permite pasar más lejos.
  • En caso de excepción en el escenario (5, 6), StringHttpMessageConverter tendrá prioridad.

Hasta ahora todo bien, pero desafortunadamente las cosas se vuelven más complicadas con ObjectToStringHttpMessageConverter . Para el tipo de devolución del manejador java.util.Collection<MetaInformation> este convertidor de mensajes informará que puede convertir este tipo a java.lang.String . La limitación proviene del hecho de que los tipos de elementos de la colección se borran y el método AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) obtiene la clase java.util.Collection<?> Para comprobar, pero cuando se trata del paso de conversión, falla ObjectToStringHttpMessageConverter . Para resolver el problema, mantenemos produces para la anotación @RequestMapping donde debe usarse el convertidor JSON, pero para forzar el tipo de contenido correcto para el manejador de excepciones, HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE atributo HttpServletRequest de 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(); }

El contexto sigue siendo el mismo que originalmente:

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

Ahora los escenarios (1,2,3,4) se manejan correctamente debido a la negociación del tipo de contenido, y los escenarios (5,6) se procesan en el manejador de excepciones.

Alternativamente, uno puede reemplazar el tipo de retorno de la colección con matrices, luego la solución n. ° 1 es aplicable nuevamente:

@RequestMapping(value = "/meta", method = RequestMethod.GET) @ResponseBody public MetaInformation[] getMetaInformations() { return myService.getMetaInformations().toArray(); }

Para discusión:

Creo que AbstractMessageConverterMethodProcessor.writeWithMessageConverters() no debe heredar la clase del valor, sino más bien de la firma del método:

Type returnValueType = returnType.getGenericParameterType();

y HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) debe cambiar a:

canWrite(Type returnType, MediaType mediaType)

o (en caso de que sea demasiado limitadores potenciales basados ​​en clases) a

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

Entonces, los tipos parametrizados podrían manejarse correctamente y la solución n. ° 1 sería aplicable nuevamente.