- Obtener vínculo
- X
- Correo electrónico
- Otras apps
En esta entrada veremos como implementar un error handling usando las anotaciones @ControllerAdvice y @ExceptionHandler de Spring Boot extendiendo de ExceptionHandlerExceptionResolver.
Nuestras respuestas serán personalizadas.
Validaremos los headers, body, tipo de dato en el header, el path, parámetros en el body y tipo, tipo de solicitud HTTP, etc.
Implementaremos los errores para las siguientes excepciones:
- HttpRequestMethodNotSupportedException
Se ejecutara cuando el tipo de método HTTP (POST,GET,PUT, etc.) no sea el que el endpoint está esperando.
- HttpMediaTypeNotSupportedException
- MissingRequestHeaderException
- HttpMessageNotReadableException
- MethodArgumentNotValidException
- ConstraintViolationException
- MethodArgumentTypeMismatchException
- NoHandlerFoundException
En otras palabras, cuando una de estas excepciones ocurra en nuestra API, automáticamente nuestro error handler detonará con una respuesta personalizada que definamos.
Nuestra respuesta tendrá la siguiente estructura:
Tendrá un mensaje y un arreglo de detalles de tipo String.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spring.remote.model; | |
import java.util.ArrayList; | |
import java.util.List; | |
public class ApiError { | |
private String mensaje; | |
private List<String> detalles; | |
public ApiError() { | |
detalles = new ArrayList<>(); | |
} | |
public void addDetalle(String detalle) { | |
detalles.add(detalle); | |
} | |
public String getMensaje() { | |
return mensaje; | |
} | |
public void setMensaje(String mensaje) { | |
this.mensaje = mensaje; | |
} | |
public List<String> getDetalles() { | |
return detalles; | |
} | |
public void setDetalles(List<String> detalles) { | |
this.detalles = detalles; | |
} | |
} |
Nuestro endpoint o Controller es el siguiente
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spring.remote.controllers; | |
import java.util.List; | |
import javax.validation.Valid; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.http.HttpHeaders; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.validation.annotation.Validated; | |
import org.springframework.web.bind.annotation.CrossOrigin; | |
import org.springframework.web.bind.annotation.DeleteMapping; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.PostMapping; | |
import org.springframework.web.bind.annotation.PutMapping; | |
import org.springframework.web.bind.annotation.RequestBody; | |
import org.springframework.web.bind.annotation.RequestHeader; | |
import org.springframework.web.bind.annotation.RestController; | |
import com.spring.remote.model.Persona; | |
import com.spring.remote.services.ServicePersona; | |
@RestController | |
@Validated | |
@CrossOrigin("*") | |
public class ControllerPersona { | |
@Autowired | |
ServicePersona servicePersona; | |
@GetMapping("/personas") | |
public ResponseEntity<List<Persona>> getAllPersonas(){ | |
List<Persona> personas = servicePersona.getPersonas(); | |
return new ResponseEntity<>(personas, new HttpHeaders(), HttpStatus.ACCEPTED); | |
} | |
@PutMapping("/persona/{id}") | |
public ResponseEntity<Object> updatePersonById(@RequestBody Persona persona, @PathVariable("id") int id) { | |
System.out.println(id + " " + persona.getNombre()); | |
servicePersona.updatePersonById(persona, id); | |
return new ResponseEntity<>(HttpStatus.ACCEPTED); | |
} | |
@DeleteMapping("/persona/{id}") | |
public ResponseEntity<Object> deletePersonById(@PathVariable int id){ | |
servicePersona.deletePersonById(id); | |
return new ResponseEntity<>(HttpStatus.ACCEPTED); | |
} | |
@PostMapping("/persona") | |
public ResponseEntity<Object> insertPerson( | |
@RequestHeader(name = "idPais", required = true) int idPais, | |
@Valid @RequestBody(required = true) List<Persona> personas){ | |
personas.forEach(p -> { | |
System.out.println(p.getId()); | |
System.out.println(p.getNombre()); | |
System.out.println(p.getApellido()); | |
p.getListTelefonos().forEach(telefono -> { | |
System.out.println("\t " + telefono); | |
}); | |
System.out.println(p.getCiudad()); | |
System.out.println(p.getDomicilio()); | |
System.out.println(p.getRfc()); | |
System.out.println(p.getCurp()); | |
}); | |
return ResponseEntity.status(HttpStatus.CREATED).body("Creado correctamente"); | |
} | |
} |
Para este ejemplo iremos haciendo la prueba con el endpoint insertPerson(), tanto el header como el body son requeridos, sólo requerimos un header de tipo int y se llama idPais.
En el objeto Persona he colocado las siguientes anotaciones para validar todos los parámetros del body
@NotEmpty nos sirve para indicarle que no deben venir null o vacíos.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spring.remote.model; | |
import java.util.List; | |
import javax.validation.constraints.NotEmpty; | |
import javax.validation.constraints.NotNull; | |
public class Persona { | |
@NotNull | |
private int id; | |
@NotEmpty | |
private String nombre; | |
@NotEmpty | |
private String apellido; | |
@NotEmpty | |
private List<String> listTelefonos; | |
@NotEmpty | |
private String ciudad; | |
@NotEmpty | |
private String domicilio; | |
@NotEmpty | |
private String rfc; | |
@NotEmpty | |
private String curp; | |
public int getId() { | |
return id; | |
} | |
public void setId(int id) { | |
this.id = id; | |
} | |
public String getNombre() { | |
return nombre; | |
} | |
public void setNombre(String nombre) { | |
this.nombre = nombre; | |
} | |
public String getApellido() { | |
return apellido; | |
} | |
public void setApellido(String apellido) { | |
this.apellido = apellido; | |
} | |
public List<String> getListTelefonos() { | |
return listTelefonos; | |
} | |
public void setListTelefonos(List<String> listTelefonos) { | |
this.listTelefonos = listTelefonos; | |
} | |
public String getCiudad() { | |
return ciudad; | |
} | |
public void setCiudad(String ciudad) { | |
this.ciudad = ciudad; | |
} | |
public String getDomicilio() { | |
return domicilio; | |
} | |
public void setDomicilio(String domicilio) { | |
this.domicilio = domicilio; | |
} | |
public String getRfc() { | |
return rfc; | |
} | |
public void setRfc(String rfc) { | |
this.rfc = rfc; | |
} | |
public String getCurp() { | |
return curp; | |
} | |
public void setCurp(String curp) { | |
this.curp = curp; | |
} | |
} |
Finalmente nuestra clase de excepciones es la siguiente:
En el caso de la excepción NoHandlerFoundException tenemos que configurar en el application.properties las siguientes lineas para que pueda responder correctamente:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.spring.remote.exceptions; | |
import java.sql.SQLException; | |
import java.util.Arrays; | |
import java.util.Set; | |
import javax.validation.ConstraintViolation; | |
import javax.validation.ConstraintViolationException; | |
import javax.validation.ElementKind; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.http.converter.HttpMessageNotReadableException; | |
import org.springframework.web.HttpMediaTypeNotSupportedException; | |
import org.springframework.web.HttpRequestMethodNotSupportedException; | |
import org.springframework.web.bind.MethodArgumentNotValidException; | |
import org.springframework.web.bind.MissingRequestHeaderException; | |
import org.springframework.web.bind.annotation.ControllerAdvice; | |
import org.springframework.web.bind.annotation.ExceptionHandler; | |
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; | |
import org.springframework.web.servlet.NoHandlerFoundException; | |
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; | |
import org.springframework.web.servlet.tags.form.ErrorsTag; | |
import com.spring.remote.model.ApiError; | |
@ControllerAdvice | |
public class RestException extends ExceptionHandlerExceptionResolver{ | |
//cuando el tipo HTTP no es correcto | |
@ExceptionHandler({HttpRequestMethodNotSupportedException.class}) | |
public ResponseEntity<ApiError> methodNotSupportedException(HttpRequestMethodNotSupportedException ex){ | |
ApiError error = new ApiError(); | |
error.setMensaje("HTTP " + ex.getMethod() + " NO soportado. Debe ser " +ex.getSupportedMethods()[0] + " para el endpoint solicitado"); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando el tipo de datos enviado no es aceptado | |
@ExceptionHandler({ HttpMediaTypeNotSupportedException.class }) | |
public ResponseEntity<ApiError> notAcceptableMediaTypeHandler(HttpMediaTypeNotSupportedException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje(ex.getMessage()); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando no existe el header requerido | |
@ExceptionHandler({ MissingRequestHeaderException.class }) | |
public ResponseEntity<ApiError> MissingRequestHeaderException(MissingRequestHeaderException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje("No existe " + ex.getParameter().getParameterName() + " en el header"); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando el body no es correcto | |
@ExceptionHandler(HttpMessageNotReadableException.class) | |
public ResponseEntity<ApiError> handleAllOtherErrors(HttpMessageNotReadableException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje(ex.getMessage()); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando @Valid tiene errores en validacion del body | |
@ExceptionHandler(MethodArgumentNotValidException.class) | |
public ResponseEntity<ApiError> MethodArgumentNotValidException(MethodArgumentNotValidException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje(ex.getMessage()); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando valores del body estan vacios o no existen | |
@ExceptionHandler(ConstraintViolationException.class) | |
public ResponseEntity<ApiError> ConstraintViolationException(ConstraintViolationException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje("Body no valido. Validar datos"); | |
ex.getConstraintViolations().stream().forEach(v -> { | |
v.getPropertyPath().forEach(e ->{ | |
if(e.getKind() == ElementKind.PROPERTY) | |
error.addDetalle(e.getName()+ " debe existir y no estar vacio"); | |
}); | |
}); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando el tipo de dato del header no coincide | |
@ExceptionHandler(MethodArgumentTypeMismatchException.class) | |
public ResponseEntity<ApiError> MethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje("El header " + ex.getName() + " debe der de tipo " + ex.getRequiredType()); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando el path no existe | |
@ExceptionHandler(NoHandlerFoundException.class) | |
public ResponseEntity<ApiError> noHandlerFoundException(NoHandlerFoundException ex) { | |
ApiError error = new ApiError(); | |
error.setMensaje("Ruta " + ex.getRequestURL() + " no existe"); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
//cuando hay un error SQL | |
@ExceptionHandler(SQLException.class) | |
public ResponseEntity<ApiError> exception(SQLException ex){ | |
ApiError error = new ApiError(); | |
error.setMensaje(ex.getMessage()); | |
error.setDetalles(Arrays.asList(""+ex)); | |
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); | |
} | |
} |
Dejo el link hacia el proyecto completo en github
- Obtener vínculo
- X
- Correo electrónico
- Otras apps
Comentarios
Publicar un comentario