Otro tema que, normalmente, dejamos con desarrollos incompletos, es la comunicacion de los errores.
Estoy de acuerdo que en explotación, no se deben ver los errores, pero, aun así, hay dos motivos para que nos preocupemos de cómo aparecen estos: primero es que durante el desarrollo, nos ahorraría mucho tiempo el ver lo que necesitamos para corregir la incidencia, y, el segundo es que en explotación, necesitamos facilitar información cuando recibimos un campo con datos erróneos…
Nuestro problema con errores 500
Si nuestro aplicativo comete un error 500, nuestra salida será algo como
y, reconoce que bonito no es, y que no nos ayuda demasiado, y… ¿que podemos hacer?
Yo os propongo que hagáis algo para conseguir esto
¿os he interesado?, pues sigamos leyendo
El @ControllerAdvice
Si queremos editar el mensaje de error y hacerlo las inteligible, necesitamos capturar el error, y luego interpretarlo.
Spring nos ofrece la anotación @ControllerAdvice que solo tenemos que anteponer a una clase, para que podamos utilizarla para la captura de errores; a continuación, nuestra primera visión. Si estamos trabajando para una API, puede que nos interese mas el utilizar la anotacion @RestControllerAdvice.
La definición del controlador
@CrossOrigin
@ControllerAdvice
public class RestResponseEntityExceptionHandler {
A nosotros nos interesan los errores que pasan por clases nuestras, no nos aporta nada ver que un error pasa por cualquier clase que no controlamos, por lo que lo primero que necesitamos es reconocer nuestras clases, y, eso lo podamos hacer gracias a los package. En mi caso, todo el aplicativo esta dentro del package «com.recursosformacion.lcs», por lo que facilitare su identificación
String MI_RUTA = "com.recursosformacion.lcs";
int MI_RUTA_LENGTH = MI_RUTA.length();
Debemos identificar también los errores que deseamos controlar, pensad que queremos extraer información, y que distintos errores pueden tener distinta información, y con distinta estructura.
Para determinar los errores que queremos interceptar, anteponemos al método la anotación @ExceptionHandler.
@ExceptionHandler(value = {
DomainException.class,
DAOException.class,
ControllerException.class,
IllegalArgumentException.class,
IllegalStateException.class,
jakarta.validation.UnexpectedTypeException.class,
org.springframework.dao.DuplicateKeyException.class,
org.springframework.web.HttpRequestMethodNotSupportedException.class,
org.springframework.web.bind.MethodArgumentNotValidException.class,
org.springframework.web.bind.MissingRequestHeaderException.class,
org.springframework.web.bind.MissingServletRequestParameterException.class,
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException.class,
java.lang.ArithmeticException.class,
org.springframework.http.converter.HttpMessageNotReadableException.class })
Este metodo no sera demasiado selectivo, y solo utilizara la estructura estandar de errores. Tambien, como he declarado ControllerAdvice, ahora, necesitare utilizar @ResponseBody, para que el mensaje salga hacia el cliente. Veamos el método
@ResponseBody
public ResponseEntity<Map<String, Object>> handleConflict(Exception ex) {
String mensaje = ex.getClass().getSimpleName() + "-" + ex.getMessage();
System.out.println(mensaje);
return montaError(ex, mensaje, HttpStatus.BAD_REQUEST);
}
Y el método que es capaz de recorrer el mensaje para extraer las clases por las que va pasando, y analizarlas para ver si pertenecen a mi paquete o no, es:
private ResponseEntity<Map<String, Object>> montaError(Exception ex, String mensaje, HttpStatus conflict) {
//*********inicializacion************************************************************
Map<String, Object> map = new LinkedHashMap<String, Object>();
String miPaquete = this.getClass().getPackageName();
System.out.println(miPaquete);
//********* Estableciendo el status de salida y el mensaje de error
map.clear();
map.put("status", 0);
map.put("message", mensaje);
/* *************************************************************************
// getStackTrace se deberia filtrar por "className": "es.rf.tienda.*/
// Array de objetos
StackTraceElement[] ste = ex.getStackTrace();
StackTraceElement[] stack = Arrays.stream(ste)
.peek(s->System.out.println(s.getClassName()))
.filter(s -> s.getClassName().length() < MI_RUTA_LENGTH ? false : s.getClassName().substring(0, MI_RUTA_LENGTH).equals(MI_RUTA))
.toArray(StackTraceElement[]::new);
map.put("stacktrace", stack);
return new ResponseEntity<Map<String, Object>>(map, HttpStatus.BAD_REQUEST);
}
Con eso, ya tenemos una version mejor de nuestro error 500. Queda a vuestra discreción si esta informacion de la haceis llegar al usuario, la grabais en un log, o la enviais por mail al equipo….
Comunicando errores del formulario
Otro tema distinto es cuando recibimos información errónea que el usuario puede corregir, en ese caso, debemos informar del error y de la posible solucion, pero, en los artículos precedentes, he estado abogando por filtros por anotación, que son muy útiles y claros para escribirlos, pero que dan la información del error, a través de una exception (MethodArgumentNotValidException.class)
Entonces, segun lo que vimos antes, solo debemos crear un @ControllerAdvice, y a continuación os propongo una posibilidad que, las aplicaciones de FrontEnd podrán manejar fácilmente.
En este caso, utilizare un @RestControllerAdvice y lo he dejado en otra clase, aunque podríamos haberlo hecho en la misma…quizás de cara a la reutilización, puede que sea mas clara la especialización
package com.recursosformacion.lcs.exception;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ControllerExceptionValidation {
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public Map<String,Object> gestionValidaciones(MethodArgumentNotValidException ex) {
Map<String,Object> errores = new HashMap<String,Object>();
errores.put("status",900);
Map<String,String> listaErrores = new HashMap<String,String>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String nombreCampo = ((FieldError) error).getField();
String mensaje = error.getDefaultMessage();
listaErrores.put(nombreCampo,mensaje);
});
errores.put("lista",listaErrores);
return errores;
}
}
Este código, recogerá una excepción de tipo MethodArgumentNotValidException y formateara una salida que en formato JSON se veria asi:
Conclusión
Todo este desarrollo lo teneis explicado con mas detalle en youTube (a partir de1/02/24) , y, aunque es conveniente que intentéis escribirlo TODO vosotros, si queréis renunciar a ello, lo teneis tambien en GitHub
Este desarrollo esta hecho para disponer de un fuente para explicar otros temas, tal y como se indica en Visión de conjunto con Spring
Relacionado
Descubre más desde Recursos para formacion
Suscríbete y recibe las últimas entradas en tu correo electrónico.