Como customizar la validación de @ConfigurationProperties en Spring
Las clases de @ConfigurationProperties en Spring se utilizan con mucha frecuencia, ya que nos permiten mapear fácilmente propiedades externalizadas a clases Java. Además, también podemos validar las propiedades al arrancar la aplicación para evitar errores en tiempo de ejecución debido a una mala configuración. Esta validación se puede hacer de forma sencilla mediante el uso de anotaciones del estándar JSR-303. Sin embargo, a veces necesitamos customizar esta validación para cumplir con nuestros requisitos.
En este tutorial vamos a crear una clase de @ConfigurationProperties validada por anotaciones JSR-303, y después mostraremos como crear validadores customizados para nuestra clase.
Setup
Para este tutorial vamos a crear una aplicación de Spring Boot con las dependencias mínimas necesarias:
Hemos decidido usar la dependencia spring-boot-starter-validation
que incluye el hibernate-validator
, que es probablemente la implementación más usada del estándar JSR-303.
A continuación, crearemos una clase Application mínima:
Esta aplicación se utilizará en las siguientes secciones para ilustrar nuestros ejemplos.
Creando nuestra clase de @ConfigurationProperties
Vamos a crear ahora la clase de @ConfigurationProperties que validaremos en nuestros ejemplos. Esta clase simplemente sirve para almacenar unas propiedades para guardar informes en un fichero y opcionalmente enviarlos por email:
Hemos utilizado la anotación @Validated para validar la propiedad targetFile con @NotBlank. Esta es una anotación de JSR-303.
Después, habilitaremos nuestra clase en nuestra Application usando la anotación @EnableConfigurationProperties:
En este punto, si tratamos de arrancar nuestra aplicación y no hemos seteado la propiedad report.targetFile obtenemos un error:
Como podemos ver, la aplicación no arranca y nos da un error bastante aclaratorio.
Creando un validador de @ConfigurationProperties customizado
Ahora que ya tenemos nuestra clase de @ConfigurationProperties lista vamos a empezar a customizar el proceso de validación. Por el momento solo estamos validando la propiedad targetFile pero también nos gustaría validar las propiedades emailSubject y recipient. Sin embargo, solo las queremos validar si sendByEmail está seteado a true. Para hacer esto tenemos que crear un validador propio ya que con las anotaciones no lo podemos hacer.
Vamos a crear ahora el validador. Para ello crearemos una clase ReportConfigValidator que implemente la interfaz de Spring Validator:
Como podemos ver, esta interfaz solo tiene 2 métodos y son bastante fáciles de implementar:
- boolean supports(Class<?> clazz): Spring lo utiliza para encontrar los validadores que son capaces de validar una clase específica. En nuestro caso, nuestro validador solo soporta la validación de nuestra clase ReportConfig
- void validate(Object target, Errors errors): valida el objeto en cuestión. Como podemos observar, tenemos que castear el objeto a nuestra clase pero si el método supports está bien implementado el casting debería ser seguro. Todos los errores de validación se almacenan en el objeto errors que recibimos como parámetro
Mencionar también que hemos hecho uso de la clase ValidationUtils que Spring nos proporciona para ayudarnos con algunas validaciones que son muy comunes.
A continuación, tenemos que decirle a Spring que utilice la clase que acabamos de crear para validar las clases de @ConfigurationProperties. Vamos a añadirlo a nuestra Application:
Es importante mencionar que el bean se tiene que llamar configurationPropertiesValidator para que Spring lo encuentre. Además, tiene que ser static, ya que Spring lo tiene que cargar muy temprano al arrancar la aplicación.
Finalmente, vamos a crear nuestro fichero de propiedades y vamos a setear la propiedad sendByEmail a true:
Y vamos a tratar de arrancar la aplicación otra vez:
Como era de esperar obtenemos los errores de validación que hemos configurado en nuestro validador más el que ya obteníamos antes de la anotación JSR-303.
Múltiples validadores
El ejemplo que hemos mostrado en la sección anterior encaja muy bien en los casos en los que solo tenemos 1 validador customizado. Desafortunadamente, si necesitamos más solo podemos crear 1 bean ConfigurationPropertiesValidator. Una solución a este problema podría ser utilizar el mismo validador para todas nuestras clases de @ConfigurationProperties pero puede resultar un poco tedioso. Una solución más apropiada sería hacer que nuestras clases de @ConfigurationProperties implementen la interfaz Validator ellas mismas.
Vamos a hacerlo con nuestra clase ReportConfig:
Como podemos ver, es el mismo código que teníamos en ReportConfigValidator pero ahora dentro de la clase de @ConfigurationProperties. Si tuviésemos más clases que validar también tendríamos que hacer que implementaran la interfaz Validator.
Ahora podemos eliminar el bean de configurationPropertiesValidator de nuestra Application e intentar arrancar la aplicación otra vez:
Como estaba previsto, obtenemos los mismos errores de validación.
Acumulando validadores
En nuestro ejemplo anterior habíamos eliminado el bean configurationPropertiesValidator, ya que habíamos movido ese código al validador de la clase de @ConfigurationProperties. ¿Pero qué pasaría si no lo borrásemos? Vamos a volver a añadirlo y arrancar de nuevo la aplicación:
Podemos observar que todos nuestros validadores han sido aplicados. Y como 2 de ellos hacen lo mismo hemos obtenido errores duplicados.
Esto ocurre porque Spring busca validadores en 3 sitios distintos:
- En el bean ConfigurationPropertiesValidator
- En el classpath si hay un validador de anotaciones JSR-303 y la clase a validar está anotada con @Validated
- En la clase a validar si implementa la interfaz Validator
Cuando Spring encuentra alguno de estos validadores los acumula y los aplica cuando se necesita validar una clase que sea soportada por ellos. En nuestro último ejemplo, hacemos uso de estos 3 tipos de validadores. Es importante tener en cuenta esto a la hora de diseñar nuestros validadores para no duplicar validaciones o que no haya contradicciones entre ellas.
Conclusión
En este tutorial hemos visto como se puede customizar la validación de clases de @ConfigurationProperties en Spring. Hemos presentado varios escenarios donde se necesitan validadores customizados y hemos propuesto soluciones para cada caso. Finalmente hemos explicado como hace Spring para buscar validadores y como los acumula cuando los encuentra.
El código fuente de los ejemplos se puede encontrar en Github.
Deja un comentario