Manejo de Excepciones en SpringMVC (II)

1
23224

En este tutorial vamos a ampliar la información que detallábamos en Manejo de excepciones en SpringMVC con el uso de @ControllerAdvice y @RestControllerAdvice.

Índice de contenidos

1. Introducción

Las anotaciones @ControllerAdvice y @RestControllerAdvice permiten utilizar las mismas técnicas de manejo de excepciones que veíamos en Manejo de excepciones en SpringMVC pero a nivel de toda la aplicación.

La anotación @ControllerAdvice aparece en la versión 3.2 de Spring y consiste en una especialización de la anotación @Component que permite declarar métodos relacionados con el manejo de excepciones que serán compartidos entre múltiples controladores, evitando así la duplicidad de código o la generación de jerarquías para que los controladores traten de manera homogénea las excepciones.

Por otro lado, la anotación @RestControllerAdvice aparece por primera vez en la versión 4.3 de Spring y se trata de una anotación que aúna @ControllerAdvice y @ResponseBody. Su funcionamiento es prácticamente idéntico a @ControllerAdvice, aunque su uso está enfocado a APIs REST, con el agregado de permitirnos establecer un contenido para el cuerpo de las respuestas los casos de error contemplados.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 17′ (2.66 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS Sierra 10.12.5
  • Entorno de desarrollo: IntelliJ 2017.1
  • Apache Maven 3.3.9

3. Uso de @ControllerAdvice y @RestControllerAdvice

3.1. @ControllerAdvice

En nuestros proyectos SpringMVC, tan solo será necesario declarar la clase encargada de gestionar las incidencias y anotarla con una de las dos anotaciones, dependiendo del caso, @CotrollerAdvice o @RetControllerAdvice.

Si no se establece lo contrario, la lógica definida para el manejo de una excepción mediante esta herramienta se aplicará de manera global.

Como es de esperar, no siempre querremos aplicar esta solución a la totalidad de los controladores sino a un subconjunto concreto de los mismos. Esta situación tiene fácil solución puesto que podemos acotar su ámbito de aplicación de diferentes maneras:

  • Seleccionando la paquetería base sobre la que queremos que aplique:

    @ControllerAdvice(basePackages={"com.autentia.biblioteca.ui.libros","com.autentia.biblioteca.ui.usuarios"})
    public class GlobalExceptionHandler {
        ...
    }
    
  • Seleccionando el conjunto de clases que extiendan una clase o implementen una interfaz:

    @ControllerAdvice(assignableTypes = {ThisInterface.class, ThatInterface.class})
    
  • Seleccionando el conjunto de clases anotadas de una manera específica:

    @ControllerAdvice(annotations= MyAnnotation.class)
    

Esta anotación da soporte, a su vez, a 3 anotaciones distintas:

  • @ExceptionHandler: Los métodos anotados de esta manera se encargarán de manejar las excepciones que se hayan detallado en la propia anotación.
  • @ModelAttribute: Esta anotación permite completar la información de un modelo expuesto vía web view.
  • @InitBinder: Permite inicializar el WebDataBinder que se utilizará para inicializar los formularios asociados al controlador.

3.2. @RestControllerAdvice

La forma de trabajar es similar a la utilizada con la @ControllerAdvice:

  • Anotamos nuestra clase de manejo general con @RestControllerAdvice.
  • Definimos el método con la lógica de control adecuada y lo anotamos con @ExceptionHandler estableciendo la excepción de la que nos vamos a encargar.
  • Establecemos el código de estado en la respuesta mediante @ResponseStatus.

El método encargado de manejar la excepción debe devolver el objeto con la información relativa a la excepción.

4. Ejemplo de aplicación

Para ilustrar el uso de estas anotaciones vamos a proceder con la creación de un proyecto web con SpringBoot, SpringMVC y Thymeleaf.

Nos ayudaremos de IntelliJ como IDE para realizar el arranque de proyecto, en concreto utilizando el asistente SpringInitialzr.

La imagen anterior muestra la estructura de carpetas resultante tras hacer uso del asistente.

A continuación, como en otras ocasiones, estableceremos las dependencias de nuestro proyecto.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.autentia.exceptionhandling</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

El asistente habrá creado una clase Application por defecto que se ajusta a nuestras necesidades.

DemoApplication.java
package com.autentia.exceptionhandling;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

Y a continuación detallamos el controlador encargado de manejar las peticiones y la plantilla html para dar forma a nuestra página.

DemoController.java
package com.autentia.exceptionhandling;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class DemoController {
    @RequestMapping("/greeting")
    public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World")String name, Model model){
        model.addAttribute("name", name);
        return "greeting";
    }
}
greeting.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Getting Started: Serving Web Content</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p th:text="'Hello, ' + ${name} + '!'" />
    </body>
</html>

Con esto ya tenemos la aplicación base sobre la que vamos a establecer nuestro @ControllerAdvice.

Si procedemos con el arranque de la aplicación y consultamos con nuestro navegador la URL adecuada, en nuestro caso http://localhost:8081/greeting podremos ver el sistema funcionando correctamente.

A continuación pasamos a configurar nuestro ControllerAdvice delcarando la clase y su método encargado de manejarlo.

DemoExceptionHandler.javas
package com.autentia.exceptionhandling;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class DemoExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String exceptionHandler(){
        return "error";
    }
}

Hemos establecido la clase anterior como controladora de excepciones a nivel global mediante @ControllerAdvice, y hemos especificado a través de la anotación @ExceptionHandler(Exception.class) que el método exceptionHandler se encargue de manejar las excepciones de tipo Exception de manera que se muestre la template error.

A continuación definimos la template error.html.

error.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>ERROR</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <p>You shouldn't be here </p>
    </body>
</html>

Por último crearemos un endpoint que tan solo lance la excepción para poder ver que el ControllerAdvice funciona correctamente:

ExceptionGeneratorController.java
package com.autentia.exceptionhandling;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionGeneratorController {
    @RequestMapping("/anotherService")
    public String generator() throws Exception {
        throw new Exception("excepcion");
    }
}

5. Conclusiones

Como hemos visto, mediante las anotaciones @ControllerAdvice y @RestControllerAdvice podemos declarar, de una manera rápida y elegante, el comportamiento que debe tener nuestra aplicación cuando se producen excepciones.

6. Referencias

  • Exception Handling in SpringMVC. Paul Chapman.
  • How to use @RestControllerAdvice for handling Exception with RestfulAPI
  • How to use Spring Exception Handler for Spring

1 COMENTARIO

  1. Me aprece buena información pero siempre los informantes de como realizar opciones de código e implementar algo para ir aprendiendo me parece genial.. pero… ¿Por qué no has realizado con @RestControllerAdvice ? claro, entiendo que para vosotros la forma mas facil es el @ControllerAdvice y así dar a entender que sois programadores con un alto nivel.. pues, yo increpo, si das una información en la cual algo ya queda «practicamente obsoleta» sobre todo para API REST que mejor que pongas la forma nueva que es implementarlo con @RestControllerAdvice, o ¿ Es que me vas a mandar que compre un curso tuyo ? ( Como haceis todos) esto ya no es información para aprender es una información escasa que no te sirve para trabajar ni mucho menos.
    Sirve para estar aburrido en casa y crear algo sencillo y enseñarselo a tus hijos, sobrinos etc..
    Realmente en un puesto de trabajo van a tirar de ti para cosas estables y funcionales, sobre todo con API Rest ya que una de las principales creaciones de SPRING fue para eso…
    Pero bueno, gracias igualmente por la información sencilla y barata.
    Seguire buscando otras personas que sí den información buena y constructiva.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad