Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

...

  1. ResponseStatusException

    Útil para devolver un error de forma correcta y rápida, sin preocuparnos de los datos devueltos.

    Se construye la excepción pasándole el status code HTTP de error deseado y Spring Boot la captura y maneja de forma automática.
    Devuelve el modelo de respuesta estándar (ver siguiente apartado).


    Ventajas: rápido, status code parametrizable.
    Desventajas: Modelo de respuesta fijo.

    Ejemplo:

    Bloque de código
    languagejava
    themeEclipse
    titleEjemplo de uso de ResponseStatusException
    try{
    .....
    throw new TestException( HttpStatus.BAD_REQUEST,"2. ResponseStatusException con FundeWebJSErrorAttributtes" );
    .....
    
    } catch ( final TestException e2 ) {
        //Se captura una excepción existente y se lanza una nueva ResponseStatusException
    	log.error( "Test de prueba de ResponseStatusException con custom FundeWebJSErrorAttributtes: {}",e2.getMessage() );
    	throw new ResponseStatusException( HttpStatus.INTERNAL_SERVER_ERROR, e2.getMessage(),e2.getCause() );
    
    } ....

    FuenteRest handleError (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/services/rest/impl/RestTestImpl.java).

    Petición:

    Bloque de código
    languagexml
    titleRequest ResponseStatusException
    https://fundewebjsshowcasedesa.um.es/api/v1.0/public/handleError/5

    Respuesta:

    Bloque de código
    languagejson
    titleResponse ResponseStatusException
    {
        "fecha": "05/10/2021 10:31:27",
        "estado": "INTERNAL_SERVER_ERROR",
        "mensaje": "2. ResponseStatusException con FundeWebJSErrorAttributtes",
        "tipoRespuesta": "CUSTOM_DefaultErrorAttributes"
    }


    1.1 ResponseStatus:

    La excepción ResponseStatusException es una evolución de otro mecanismo, la notación @ResponseStatus .
    Por comodidad se recomienda usar ResponseStatusException, pero es posible seguir usando esta notación declarándola nuestras excepciones y lanzándolas desde nuestra API REST directamente. 
    Ver: Ejemplo ResponseStatus (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/exceptions/TestResponseStatusException.java).

    Ejemplo ejecución:

    Bloque de código
    languagejava
    themeEclipse
    titleEjemplo declaración y uso @ResponseStatus
    ...
    @ResponseStatus( code = HttpStatus.BAD_REQUEST, reason = "Reason en ResponseStatus", value = HttpStatus.INTERNAL_SERVER_ERROR )
    public class TestResponseStatusException extends RuntimeException
    ....
    /
    try{
    ...   
    	throw new TestResponseStatusException( HttpStatus.BAD_REQUEST, "1. Notación ResponseStatus con FundeWebJSErrorAttributtes" );
    ...
    } catch ( final TestResponseStatusException e1 ) {
    	log.error( "Test de prueba de notación @ResponseStatus con custom FundeWebJSErrorAttributtes: {}",e1.getMessage() );
    	throw e1;
    }
    ...


    Bloque de código
    titleRequest con @ResponseStatus
    https://fundewebjsshowcasedesa.um.es/api/v1.0/public/handleError/1


    Bloque de código
    languagejson
    titleRespuesta con @ResponseStatus
    {
        "fecha": "05/10/2021 10:45:25",
        "estado": "BAD_REQUEST",
        "mensaje": "1. Notación ResponseStatus con FundeWebJSErrorAttributtes",
        "tipoRespuesta": "CUSTOM_DefaultErrorAttributes"
    }



  2. DefaultErrorAttributes

    Por defecto Spring usa el modelo de respuesta (los campos que se devuelven) devuelto por la clase DefaultErrorAttributes.

    Podemos modificar el modelo de respuesta estándar extendiendo esta clase (y anotándola con @Component).

    Cambiar el modelo de respuesta implica que, si no se usa otro mecanismo, el nuevo modelo será devuelto en todas las situaciones de error.

    Ejemplo de fichero extendido:

    Bloque de código
    languagejava
    themeEclipse
    titleCustom DefaultErrorAttributes
    @Component
    public class FundeWebJSErrorAttributes extends DefaultErrorAttributes {
    
    	private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager
    			.getLogger( FundeWebJSErrorAttributes.class );
    
    	public static final String TIPO_RESPUESTA = "CUSTOM_DefaultErrorAttributes";
    
    	@Override
    	public Map<String, Object> getErrorAttributes( WebRequest webRequest, boolean includeStackTrace ) {
    		final Map<String, Object> allErrorAttributes = super.getErrorAttributes( webRequest, includeStackTrace );
    		final Map<String, Object> errorAttributes = new HashMap<>();
    
    		final int statusCode = ( int ) allErrorAttributes.get( "status" );
    		errorAttributes.put( "tipoRespuesta", TIPO_RESPUESTA );
    		errorAttributes.put( "estado", HttpStatus.valueOf( statusCode ) );
    		errorAttributes.put( "fecha",
    				LocalDateTime.now().format( DateTimeFormatter.ofPattern( "dd/MM/yyyy HH:mm:ss" ) ) );
    
    		String mensaje = "";
    
    		final Throwable throwable = getError( webRequest );
    
    		if ( throwable instanceof ResponseStatusException ) {
    			final ResponseStatusException responseStatusException = ( ResponseStatusException ) throwable;
    			mensaje = responseStatusException.getReason() == null ? "" : responseStatusException.getReason();
    
    		} else if ( throwable instanceof ConstraintViolationException ) {
    			webRequest.setAttribute( "status", HttpStatus.BAD_REQUEST.value(), 1 );
    			errorAttributes.put( "estado", HttpStatus.BAD_REQUEST.toString() );
    			mensaje = throwable.getMessage() == null ? "" : throwable.getMessage();
    		} else {
    
    			if ( ( throwable != null ) && ( throwable.getMessage() != null ) ) {
    				mensaje = throwable.getMessage() == null ? "" : throwable.getMessage();
    			}
    
    		}
    
    		errorAttributes.put( "mensaje", mensaje );
    
    		return errorAttributes;
    	}
    
    }
    
    

    Fuente: FundeWebJSErrorAttributes.java

    Advertencia

    No se recomienda desarrollar modelos propios de respuesta estándar hasta que se complete la integración con el RFC 7807 Problem Details for HTTP APIs por parte de MNCS.



  3. ExceptionHandler

    En los métodos anteriores se ha explicado cómo lanzar excepciones en nuestros servicios y modificar el modelo de respuesta.
    A partir de este punto se explicará cómo maneja Spring las excepciones y cómo modificar este mecanismo, lo que permitirá gestionar las respuestas, definir modelos de respuesta diferentes dependiendo de cada excepción, y sobreescribir el comportamiento estándar en situaciones de error no controladas.

    Como primera aproximación a este enfoque tenemos la notación @ExceptionHandler

    Ventajas:  Permite manejar una excepción y modificar los valores de la respuesta http así como el modelo.
    Desventajas: solo funciona de forma local en el controlador REST donde se haya definido.

    Ejemplo de uso: Para probarlo simplemente se define un POJO con los datos para el body de la respuesta, se crea el método anotado con ExceptionHandler y se lanza la excepción a capturar.

    Bloque de código
    languagejava
    themeEclipse
    titlePOJO para definir el modelo de respuesta
    @Data
    @RequiredArgsConstructor
    @AllArgsConstructor
    @Builder
    public class TestLocalHandlerError {
    	
    	@NonNull
    	private HttpStatus estado;
    	@JsonFormat(shape = Shape.STRING, pattern = "dd/MM/yyyy hh:mm:ss")
    	private LocalDateTime fecha;
    	@NonNull
    	private String mensaje;
    	@Builder.Default
    	private String tipoRespuesta = "CUSTOM_LocalHandler";
    
    }
    
    


    FuenteTestLocalHandlerError.java (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/error/TestLocalHandlerError.java)

    Bloque de código
    languagejava
    themeEclipse
    titleExceptionHandler
    @ExceptionHandler( TestLocalHandlerException.class )
    public ResponseEntity<TestLocalHandlerError> handleTestLocalHandler( TestLocalHandlerException ex ) {
    
    	final TestLocalHandlerError error = TestLocalHandlerError.builder()
    			.estado( HttpStatus.INTERNAL_SERVER_ERROR ).fecha( LocalDateTime.now() )
    			.mensaje( ex.getMessage() ).build();
    
    	return ResponseEntity.status( HttpStatus.INTERNAL_SERVER_ERROR ).body( error );
    }

    Fuente: Rest Controller Test (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/services/rest/impl/RestTestImpl.java)

    Bloque de código
    languagejava
    themeEclipse
    titleUso en servicio REST
    ...
    try{
       throw new TestLocalHandlerException("3. Excepción con handler local y modelo custom TestLocalHandlerError" );
    } catch ( final TestLocalHandlerException e3 ) {
       log.error( "Test de prueba de excepción con handler en el propio controller y modelo TestLocalHandlerError: {}", e3.getMessage() );
       throw e3;
    }
    ...


    Ejemplo llamada:

    Bloque de código
    titleURL de ejemplo de petición
    https://fundewebjsshowcasedesa.um.es/api/v1.0/public/handleError/3


    Bloque de código
    languagejson
    titleRespuesta
    {
        "estado": "INTERNAL_SERVER_ERROR",
        "fecha": "05/10/2021 12:37:46",
        "mensaje": "3. Excepción con handler local y modelo custom TestLocalHandlerError",
        "tipoRespuesta": "CUSTOM_LocalHandler"
    }



  4. ControllerAdvice

    El principal inconveniente del mecanismo anterior es que, si queremos manejar la misma excepción en varios controllers, había que escribir el mismo método en cada uno de ellos.


    La forma de manejar todas las expeciones de forma global y conjunta en toda nuestra aplicación se realiza a través de la notación @RestControllerAdvice.


    Info

    La notación RestControllerAdvice engloba las notaciones ControllerAdvice (que registra la clase en el contexto de Spring para que pueda acceder a todos los controllers) y ResponseBody (para que los pojos enviados en la respuesta se interpreten como JSON).


    Info

    Es posible declarar varios RestControllerAdvice para dividir el manejo de excepciones por paquetes, clases, anotaciones, etc. 
    Por defecto, si no se pasan parámetros en la declaración de la notación @RestControllerAdvice, los manejadores serán comunes a toda la aplicación.


    Ejemplo de uso:

    Bloque de código
    languagejava
    themeEclipse
    titleExcepción a capturar
    public class TestControllerAdviceException extends RuntimeException {
    
    	private static final long serialVersionUID = 1L;
    
    	public TestControllerAdviceException( String msg ) {
    		super( msg );
    	}
    }

    Fuente: TestControllerAdviceException.java (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/exceptions/TestControllerAdviceException.java)

    Bloque de código
    languagejava
    themeEclipse
    titlePOJO con modelo de respuesta de error
    @Data
    @RequiredArgsConstructor
    @AllArgsConstructor
    @Builder
    public class TestControllerAdviceError {
    	
    	@Nonnull
    	private HttpStatus estado;
    	@JsonFormat(shape = Shape.STRING, pattern = "dd/MM/yyyy hh:mm:ss")
    	private LocalDateTime fecha;
    	@Nonnull
    	private String mensaje;
    	@Builder.Default
    	private String tipoRespuesta = "CUSTOM_ControllerAdvice";
        private List<String> erroresValidacion;
    }

    Fuente: TestControllerAdviceError.java (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/error/TestControllerAdviceError.java)

    Bloque de código
    languagejava
    themeEclipse
    titleClase RestControllerAdvice
    @RestControllerAdvice( basePackages = "es.um.atica.fundewebjsshowcasebackend" )
    public class TestControllerAdvice {
    
    	/**
    	 * Handler de TestControllerAdviceException
    	 *
    	 * @param ex
    	 * @return
    	 * @see TestControllerAdviceException
    	 * @see RestTestImpl
    	 */
    	@ExceptionHandler( TestControllerAdviceException.class )
    	public ResponseEntity<TestControllerAdviceError> handleTestControllerAdvice( TestControllerAdviceException ex ) {
    
    		final TestControllerAdviceError error = TestControllerAdviceError.builder()
    				.estado( HttpStatus.INTERNAL_SERVER_ERROR ).fecha( LocalDateTime.now() ).mensaje( ex.getMessage() )
    				.build();
    
    		return ResponseEntity.status( HttpStatus.INTERNAL_SERVER_ERROR ).body( error );
    	}}

    Fuente: TestControllerAdvice.java (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/handler/TestControllerAdvice.java)

    Bloque de código
    languagejava
    themeEclipse
    titleLanzar excepción en servicio
    try{
    ...
    throw new TestControllerAdviceException("4. Excepción con ControllerAdvice y modelo custom TestControllerAdviceError" );
    ...
    } catch ( final TestControllerAdviceException e4 ) {
    	log.error("Test de prueba de excepción con handler en el ControllerAdvice y modelo TestControllerAdviceError: {}", e4.getMessage() );
    	throw e4;
    }

    Fuente: Rest Controller Test (https://gitlab.um.es/mncs/fundewebjsshowcase-api/-/blob/desarrollo/src/main/java/es/um/atica/fundewebjsshowcasebackend/services/rest/impl/RestTestImpl.java)

    Ejemplo de petición:

    Bloque de código
    titleURL de ejemplo
    https://fundewebjsshowcasedesa.um.es/api/v1.0/public/handleError/4


    Bloque de código
    languagejson
    titleRespuesta JSON
    {
        "estado": "INTERNAL_SERVER_ERROR",
        "fecha": "05/10/2021 01:37:25",
        "mensaje": "4. Excepción con ControllerAdvice y modelo custom TestControllerAdviceError",
        "tipoRespuesta": "CUSTOM_ControllerAdvice",
        "erroresValidacion": null
    }





  5. ControllerAdvice + ResponseEntityExceptionHandler

    Permite extender y sobreescribir el manejo de las excepciones internas que realiza Spring Boot de forma automática.
    Poner listado de excepciones.

    Poner código de servicio y enlaces a gitlab.
    Poner petición y respuesta ejemplo de postman.


  6. Bean Validation

    Configuración y uso de Bean Validation.
    Handlers a sobreescribir para modificar respuesta HTTP o modelo.
    Poner código de servicio y enlaces a gitlab.
    Poner petición y respuesta ejemplo de postman.

...