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 queMappingJackson2HttpMessageConverter
está vinculado aapplication/json
. - Cuando
@RequestMapping
proporcionaproduces = ...
, este valor se almacena enHttpServletRequest
(verRequestMappingInfoHandlerMapping.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 soloMappingJackson2HttpMessageConverter
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.