En este artículo, os explicaremos sobre las reglas de validación utilizando Java Bean Validator.
Cuando nos encontramos en el proceso de diseño de una API, uno de los puntos claves es la validación de los datos de entrada.
Esto nos ayuda a que la información recibida sea la que deseamos, corroborando que la información enviada es segura, íntegra y confiable.
¿Qué es Java Bean Validator?
Java Bean Validator es una especificación de Java para validar objetos mediante el uso de anotaciones.
Esta, fue añadida por primera vez en Java 6 y ha seguido evolucionando y mejorando hasta la versión actual que es la 2.0, incluida en la versión de Java 8.
Aunque nosotros lo ilustraremos en la capa de API, la especificación es independiente de la capa en la que se implemente.
Dependencias
Para poder utilizar las anotaciones de Java Bean Validator tenemos que añadir como dependencia a nuestro proyecto hibernate-validator.
Tipos de anotaciones
Validación de booleanos
- AssertFalse: Se utiliza para validar que un campo de tipo boolean deba ser false.
- AssertTrue: Se usa para comprobar que un campo de tipo boolean deba ser true.
Validación de números
- Max: Se utiliza para validar que el número introducido sea menor o igual al número establecido. Esta anotación no está soportada por los tipos que aceptan coma flotante, debido a los errores producidos en el redondeo.
- Min: Se usa para validar que el número introducido sea mayor al número mínimo establecido. Esta anotación no está soportada por los tipos que aceptan coma flotante, debido a los errores producidos en el redondeo.
- DecimalMax: Se emplean para comprobar que el valor introducido debe ser menor o igual al número decimal especificado. Si se quiere excluir como correcto que el valor introducido es igual al esperado, hay que indicarlo con la propiedad inclusive, poniéndolo a false.
- DecimalMin: Se usa para validar que el valor introducido debe ser mayor o igual que el número decimal especificado. Si se quiere excluir como correcto que el valor introducido es igual al esperado, hay que indicarlo con la propiedad inclusive, poniéndolo a false.
- Digits: Se usa para validar el número de dígitos de un campo. Para validar el número de dígitos de la parte entera, hay que utilizar la propiedad integer y para la parte fraccionada, fraction. Ambas deben ser superiores a 0.
- Negative: Se emplea para validar que el número introducido sea menor a 0.
- NegativeOrZero: Se utiliza para validar que el número introducido sea menor o igual a 0.
- Positive: Se emplea para validar que el número introducido sea mayor a 0.
- PositiveOrZero: Se utiliza para validar que el número introducido sea menor o igual a 0.
Validación de textos
- Email:Se utiliza para validar que un campo de tipo texto contiene el formato de correo electrónico válido.
- NotBlank: Se usa para validar que un campo de tipo texto tenga al menos un carácter imprimible.
- Pattern:Se utiliza para especificar qué patrón debe tener el valor introducido en un campo de tipo texto.
Validación de fechas
- Future: Se emplea para validar que una fecha, hora o instante específico está en el futuro en relación con la hora del sistema donde se ejecuta.
- FutureOrPresent: Se usa para validar que una fecha, hora o instante específico está en el futuro o presente en relación con la hora del sistema donde se ejecuta.
- Past: Se emplea para validar que una fecha, hora o instante específico está en el pasado en relación con la hora del sistema donde se ejecuta.
- PastOrPresent: Se usa para validar que una fecha, hora o instante específico está en el pasado o presente en relación con la hora del sistema donde se ejecuta.
Validación de objetos
- NotEmpty: Se usa para validar que el número de elementos o caracteres sea mayor a 0.
- NotNull: Se utiliza para especificar que un campo no sea null.
- Null: Se usa para especificar que un campo sea null.
- Size: Se utiliza para validar que el número de elementos o caracteres entre el rango especificado.
Personalización de mensajes de error
Todas las anotaciones tienen mensajes de error configurados en los ficheros ValidationMessage_XX.properties, que contiene la librería. Donde XX representa el idioma en ISO2.
Si se quiere, se pueden personalizar los mensajes de error, rellenando la propiedad message.
Creación de anotaciones personalizadas
Además de las anotaciones anteriores, podemos crear nosotros mismos anotaciones adecuadas a nuestras necesidades.
Definición de la anotación
Para realizar esto, lo primero que tenemos que hacer es crear la anotación y configurarla para saber cómo y cuándo debe actuar.
Esto se hace con las siguientes anotaciones:
- @Constraint: Establece la clase que tendrá la lógica de validación
- @Target: Determina en qué tipo de elementos se puede aplicar
- @Retention: Establece cuando se activará, si en el código fuente, en tiempo de compilación o en tiempo de ejecución
Lo otro que debemos añadir son los atributos que son obligatorios para todas las anotaciones de validación, que son:
- message: Propiedad en el que definimos la clave de internacionalización del mensaje por defecto de nuestro validador.
- groups: Atributo que permite a la especificación agrupar validaciones. Debe ser por defecto un array vacío de Class<?>.
- playload: Propiedad que sirve para proporcionar información adicional o metadatos sobre la restricción.
Además de los atributos mencionados, podemos añadir los nuestros propios que serán los que admitan nuestra anotación de validación.
En el ejemplo que veremos a continuación, comprobaremos el número de teléfono y añadiremos length y prefix como propiedades de nuestra anotación.
La primera, al no estar inicializado como el resto de propiedades, cuando se utilice, habrá que establecer un valor. Sin embargo, la segunda, al tener un valor por defecto, no hace falta darle uno nuevo si no queremos.
El objetivo de la anotación List, que aparece dentro de la anotación que hemos creado, sirve para que la anotación que hemos creado se pueda definir varias veces sobre un mismo elemento, como por ejemplo, un atributo de una clase.
Validador de la anotación
Una vez creada la anotación, necesitamos implementar el validador para la misma. Para ello, debemos crear una clase que implemente la interfaz ConstraintValidator, que tiene 2 tipos genéricos que debemos definir, uno es la anotación que hemos creado y el otro el tipo de dato que queremos validar:
Esta interfaz tiene 2 métodos importantes:
- initialize(A constraintAnnotation): Este método se utiliza para cargar cualquier configuración que necesite el validador, como los valores de los atributos de la anotación.
- isValid(T value, ConstraintValidatorContext context): Este método es el que implementaremos ya que contendrá la lógica de validación.
Un ejemplo de cómo hacerlo, sería el siguiente:
Internacionalización del mensaje de error
Aunque podemos definir el mensaje de error a devolver directamente en la anotación, también podemos crear una clave de internacionalización para tener el mensaje definido en distintas lenguas.
Todos los mensajes personalizados se deben escribir en un fichero llamado ValidationMessages.properties que ubicaremos en el classpath de nuestro proyecto.
En función del idioma a internacionalizar el fichero tendrá el sufijo con los idiomas en ISO2 soportados (ValidationMessages_en.properties, ValidationMessages_es.properties, … )
En nuestro caso, registraremos la siguiente clave:
Lo que vaya entre llaves, serán nuestras variables o las expresiones que queremos que se evalúen.
Si no sobreescribimos la propiedad message o el sistema no utiliza este idioma, aparecerá el código de internacionalización como mensaje de error.
Formas de realizar las validaciones
Para que estas anotaciones entren en acción hay varias formas de hacerlo, pero nos centraremos en las dos más principales.
En ambos casos, se debe añadir la dependencia mencionada anteriormente y se tiene que crear una clase con atributos con las anotaciones de validación deseadas.
Validación manual
Para realizar la validación manual debemos utilizar la API de validación. Esta, nos proporciona una serie de interfaces para realizar la validación.
Primero, debemos utilizar el objeto ValidatorFactory para obtener una instancia de Validator. Después, lo usamos para verificar el objeto que deseemos y nos devolverá una lista con todas las restricciones violadas.
Para ejemplificar esto, proporcionamos un ejemplo de código detallado:
Validación automática en Spring
Esta forma de validación se utiliza principalmente para validar la entrada de una API.
Para realizarla, hay que poner en cada parámetro del método la anotación Valid o Validated
De esta forma se delegará la gestión y el manejo de errores a Spring.
Pero si queremos formatear y devolver los errores producidos en la validación, debemos crear un Handler con la anotación RestControllerAdvice, que se utiliza para manejar excepciones en una aplicación web basada en Spring y devolver el resultado en la salida de la API.
En este caso solo nos interesa interceptar la excepción MethodArgumentNotValidException que se lanza cuando un argumento anotado con @Valid o @Validated no cumple con las restricciones definidas en las anotaciones del objeto.
Para ilustrar esto, exponemos un ejemplo de código detallado:
Resumen
En este artículo hemos hablado sobre:
- Qué es Java Bean Validation.
- Qué tipo de anotaciones existen.
- Cómo crear una validación personalizada.
- Qué tipo de implementaciones de las validaciones existen.
Todo lo que hemos comentado, lo dejamos implementado en un pequeño proyecto en nuestro repositorio de GitLab.