Uso de ConfigurationProperties en Spring Boot
En esta nueva entrada vamos a profundizar un poco en el uso de ConfigurationProperties en Spring Boot para poder cargar propiedades de nuestros ficheros de configuración. Anteriormente pudimos ver como podemos externalizar la configuración a través de Kubernetes o con Git y Vault.
Además veremos diferentes usos de @ConfigurationProperties en ejemplos con Spring Boot. Si quieres descargar directamente los ejemplos los puedes ver en nuestro Github.
Uso de @Value vs @ConfigurationProperties
Una de las formas más típicas de utilizar una propiedad de nuestro .yml o .properties en una clase es haciendo uso de @Value. De esta forma podremos hacer uso de la propiedad y su valor. Esta aproximación (que no es el objetivo de esta entrada), es buena en los casos que únicamente necesitamos cargar una propiedad.
Por ejemplo tenemos un fichero que se llama propiedades.properties en nuestra carpeta resources, en el que tenemos una propiedad email, vamos a ver como cargarlo.
email=refactorizando.web@gmail.com name=refactorizando
@Component @PropertySource("classpath:propiedades.properties") public class GlobalProperties { @Value("${name}") private String name; @Value("${email}") private String email; //getters and setters }
El ejemplo anterior lo podríamos cargar fácilmente con @ConfigurationProperties, y hacemos uso de de @PropertySource para indicar donde se encuentra nuestro fichero de propiedades. En el caso en el que usemos el application.yml o application.properties que vienen por defecto no haría falta.
@Component @PropertySource("classpath:propiedades.properties") @ConfigurationProperties public class GlobalProperties { private String name; private String email; //getters and setters }
El @Component no haría falta añadirlo a partir de la versión 2.2 de Spring Boot, y se podría usar ConfigurationPropertiesScan. Lo veremos a continuación.
Uso de @ConfigurationProperties en Spring Boot
El uso de @ConfigurationProperties es perfecto para aquellas propiedades que forman una estructura jerárquica, además de esta manera podemos aislar las propiedades relacionadas en una única clase y así tener clases diferentes para agrupar las propiedades. Es decir, todas las propiedades con prefijo común estarán agrupadas en una clase.
Hasta la versión 2.2 de Spring Boot, era necesario añadir @Component o @Configuration para poder mapear esas propiedades en una clase, pero a partir de esta versión esa dependencia se ha eliminado simplificando la carga.
@ConfigurationProperties(prefix = "proxy") public class ConfigProperties { private String hostName; private int port; // standard getters and setters }
Estas propiedades se encuentran en nuestro fichero application.yml:
#Simple properties proxy.hostname=host@mail.com proxy.port=9000
Estas propiedades serán escaneadas por la anotación principal @SpringBootApplication y las cargará en la clase en la que hemos añadido @ConfigurationProperties. Además podríamos indicar cualquier otra ruta de nuestro proyecto con la anotación @ConfigurationPropertiesScan.
@SpringBootApplication @ConfigurationPropertiesScan("com.refactorizando.example.configurationproperties") public class Application{ public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Carga de propiedades en Maps y List con @ConfigurationProperties
En algunas ocasiones necesitaremos agrupar diferentes propiedades en maps o list de nuestro fichero de propiedades. Por ejemplo, podemos querer generar clientes (WebClient) con diferentes propiedades de conexión. Para estos casos será útil poder hacer uso de un Map.
configuration: clients: product: host: "serviceA" port: 9000 shop: host: "serviceB" port: 8080
Para el caso anterior vamos a hacer uso de un Map para cargar los dos clientes que vamos a configurar, además crearemos una clase adicional para el configuration:
public class ClientProperties{ private String host: private int port: }
@ConfigurationProperties(prefix = "configuration") public class ConfigProperties { private Map<String, ClientProperties> clients; // standard getters and setters }
Uso de @ConfigurationProperties en @Bean
Para aquellas ocasiones en las que la generación o creación de un bean depende de unas propiedades, podemos cargar las propiedades en tiempo de creación del @Bean.
Partiendo de la clase creada anteriormente de ClientProperties vamos a crear un Bean de un cliente en concreto:
@Configuration public class ConfigProperties { @Bean @ConfigurationProperties(prefix = "configuration.clients.products") public ClientProperties client() { return new ClientProperties(); } }
Propiedades inmutables con @ConfigurationProperties en Spring Boot
A partir de la versión 2.2 de Spring Boot se ha incorporado una nueva propiedad para hacer nuestras propiedades inmutables. Es decir, permite crear instancias que no podrán ser modificadas. Para poder hacer uso de esta funcionalidad se añadirá @ConstructorBinding, junto con @ConfigurationProperties.
Para poder hacer que nuestras propiedades sean inmutables, será necesario marcarlas con final y crear su constructor. De esta manera no podrán ser modificadas ya que no tienen métodos setter para poder modificarlas.
Para poder hacer uso de @ConstructorBinding será necesario añadir la anotación @EnableConfigurationProperties.
Vamos a ver como quedaría nuestras propiedades inmutables haciendo uso de lombok:
@ConfigurationProperties(prefix = "configuration.clients.products") @ConstructorBinding @Getter @RequiredArgsConstructor public class InmutableClient{ private final String host; private final int port; }
@ConfigurationProperties con Java 16 y Records
Como ya comentamos en un blog sobre los records de Java 16, estos nos va a permitir crear «registros» inmutables en nuestras clases. Por lo que el punto anterior podríamos simplificarlos haciendo uso de records.
@ConstructorBinding @ConfigurationProperties(prefix = "configuration.clients.products") public record InmutableClient(String host, int port) { }
El uso de records de Java 16 para cargar propiedades es mucho más sencillo y limpio que haciendo uso de versiones anteriores de Java. Además nos ayuda a simplificar nuestro código.
Como información adicional, a partir de Spring Boot 2.6, podríamos eliminar la anotación @ConstructorBinding si se tiene un único constructor.
Validación con @ConfigurationProperties en Spring Boot
Para realizar validaciones sobre nuestras clases podemos hacer uso del formato JSR-303 con nuestro @ConfigurationProperties. De esta manera podremos especificar restricciones o validaciones haciendo uso de los paquetes de javax.validation o creando nuestros propios validadores.
Si partimos del siguiente fichero de propiedades:
configuration: clients: product: host: "serviceA" port: 7000
Vamos a añadir una validación a su correspondiente clase de carga de propiedades:
@ConfigurationProperties(prefix = "configuration.clients.product") @Getter @Setter @Validated public class ValidatorProperties { @NotEmpty private String host; @Max(9000) @Min(8000) private int port; }
Para hacer uso de la validación hay que tener en cuenta que se debe añadir @Validated.
Y al ejecutar el código obtendremos el siguiente error:
Conclusión
El uso de ConfigurationProperties en Spring Boot nos va a facilitar la carga de propiedades así como la creación de beans o diferentes configuraciones en nuestras aplicaciones o servicios.
Puedes encontrar los diferentes ejemplos que hemos visto aquí en nuestro github.
Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!