Contactez-nous

Liaison de type sécurisé avec `@ConfigurationProperties`

Maîtrisez @ConfigurationProperties pour lier de manière structurée et type-safe vos fichiers de configuration à des objets Java (POJOs) dans Spring Boot. Améliorez la robustesse.

Au-delà de @Value : structurer la configuration

Si l'annotation @Value est pratique pour injecter des propriétés de configuration individuelles, elle montre ses limites lorsque vous devez gérer un ensemble de propriétés liées structurellement. Par exemple, configurer une connexion à une base de données implique une URL, un nom d'utilisateur, un mot de passe, des options de pool, etc. Injecter chacune de ces valeurs séparément avec @Value dans plusieurs classes peut devenir répétitif, difficile à maintenir et sujet aux erreurs (fautes de frappe dans les clés, incohérences).

Spring Boot propose une solution beaucoup plus élégante et robuste : l'annotation @ConfigurationProperties. Son objectif est de permettre la liaison type-safe (type-safe binding) de groupes de propriétés de configuration directement sur des objets Java dédiés (souvent des POJOs - Plain Old Java Objects ou des Java Beans). Cela transforme un ensemble de clés/valeurs potentiellement dispersées en un objet structuré, facile à utiliser et à valider.

Le mécanisme de base : prefixe et POJO

Le fonctionnement de @ConfigurationProperties repose sur deux éléments principaux :

  • Un préfixe (prefix) : Il indique la partie commune des clés de propriétés dans vos fichiers de configuration (.properties ou .yml) qui doivent être liées à l'objet.
  • Une classe Java (POJO) : Cette classe contient des champs dont les noms correspondent (en utilisant une convention de nommage flexible, voir "Relaxed Binding" plus bas) aux parties des clés de propriétés qui suivent le préfixe.

Supposons la configuration suivante dans application.yml :

app:
  name: Super Application
  version: "2.1"
  notifications:
    email: admin@example.com
    enabled: true
    retry-attempts: 3 # Note: kebab-case

Vous pouvez créer une classe Java pour représenter cette configuration :

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app") // Le préfixe correspond à la clé racine dans YAML/Properties
public class AppProperties {

    private String name;
    private String version;
    private Notifications notifications = new Notifications(); // Objet imbriqué

    // Getters et Setters sont nécessaires pour la liaison aux champs
    // (ou utiliser la liaison par constructeur - voir plus bas)
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getVersion() { return version; }
    public void setVersion(String version) { this.version = version; }

    public Notifications getNotifications() { return notifications; }
    public void setNotifications(Notifications notifications) { this.notifications = notifications; }

    // Classe interne (ou externe) pour les propriétés imbriquées
    public static class Notifications {
        private String email;
        private boolean enabled;
        private int retryAttempts; // Note: camelCase correspond à retry-attempts

        // Getters et Setters...
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }

        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }

        public int getRetryAttempts() { return retryAttempts; }
        public void setRetryAttempts(int retryAttempts) { this.retryAttempts = retryAttempts; }
    }
}

Lorsque Spring Boot démarre, il va repérer la classe AppProperties (si elle est correctement activée, voir section suivante), lire les propriétés commençant par app. dans la configuration, et automatiquement appeler les setters correspondants pour peupler les champs de l'objet AppProperties. La liaison est type-safe : Spring essaiera de convertir les valeurs de configuration (qui sont souvent des chaînes) vers les types des champs Java (String, int, boolean, objets imbriqués, listes, etc.).

Activer la liaison : @EnableConfigurationProperties et @ConfigurationPropertiesScan

Pour que Spring Boot prenne en compte votre classe annotée avec @ConfigurationProperties et effectue la liaison, vous devez l'activer. Il existe deux manières principales :

  • @EnableConfigurationProperties : Ajoutez cette annotation sur l'une de vos classes de configuration (annotée avec @Configuration) et spécifiez la ou les classes de propriétés à activer.
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(AppProperties.class) // Active AppProperties
public class AppConfig {
    // ... autres configurations @Bean si nécessaire
}
  • @ConfigurationPropertiesScan : Une approche plus récente et souvent plus simple. Ajoutez cette annotation sur votre classe principale (@SpringBootApplication) ou une classe de configuration. Spring Boot scannera alors automatiquement les packages spécifiés (par défaut, le package de la classe annotée et ses sous-packages) à la recherche de classes annotées avec @ConfigurationProperties et les enregistrera comme des beans. Pour que cela fonctionne, la classe de propriétés doit elle-même être un bean (par exemple, en l'annotant aussi avec @Component, bien que ce ne soit pas strictement nécessaire si seule la liaison est voulue via le scan).
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan // Scanne pour @ConfigurationProperties
public class MonApplication {

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

}

Une fois la classe de propriétés activée et enregistrée comme bean, vous pouvez l'injecter comme n'importe quel autre bean Spring dans vos services ou contrôleurs :

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

    private final AppProperties appProperties;

    @Autowired
    public NotificationService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    public void sendNotification(String message) {
        if (appProperties.getNotifications().isEnabled()) {
            System.out.println("Envoi de notif à " + appProperties.getNotifications().getEmail());
            System.out.println("Message: " + message);
            System.out.println("Tentatives max: " + appProperties.getNotifications().getRetryAttempts());
        } else {
            System.out.println("Notifications désactivées.");
        }
    }
}

Avantages clés : structure, validation et refactoring

L'utilisation de @ConfigurationProperties offre plusieurs avantages majeurs par rapport à @Value :

  • Structure et Groupement : Regroupe logiquement les propriétés liées dans une seule classe, améliorant la lisibilité et l'organisation du code.
  • Type Safety : La liaison est effectuée vers des types Java spécifiques, détectant les erreurs de type à la compilation ou au démarrage plutôt qu'à l'exécution.
  • Validation : Vous pouvez utiliser les annotations de validation standard de Java (JSR-303/JSR-380, comme @NotNull, @Min, @Max, @Pattern, @Email, @URL) directement sur les champs de la classe de propriétés. En ajoutant @Validated sur la classe de propriétés elle-même, Spring Boot validera automatiquement les valeurs au démarrage et lèvera une exception si une contrainte n'est pas respectée, assurant une configuration correcte (fail-fast).
  • Refactoring Facilité : Si vous renommez une propriété dans votre classe Java, les outils de refactoring de votre IDE peuvent vous aider à mettre à jour les utilisations. Avec @Value et des chaînes de caractères, le refactoring est manuel et plus risqué.
  • Support des Structures Complexes : Gère nativement les objets imbriqués, les listes (List) et les maps (Map) de manière très naturelle.
  • Liaison Relaxée (Relaxed Binding) : Spring Boot est flexible sur la casse et le format des clés de propriétés. Par exemple, app.notifications.retry-attempts (kebab-case), app.notifications.retry_attempts (snake_case), ou APP_NOTIFICATIONS_RETRYATTEMPTS (variable d'environnement) peuvent tous être liés au champ Java retryAttempts (camelCase). Cela facilite l'adaptation à différentes conventions de nommage.

Pour la validation, assurez-vous d'avoir la dépendance spring-boot-starter-validation (souvent incluse par spring-boot-starter-web) et annotez votre classe :

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties(prefix = "app")
@Validated // Active la validation JSR-380
public class AppProperties {

    @NotBlank // Ne doit pas être null ou vide
    private String name;
    // ...
    public static class Notifications {
        @Email // Doit être une adresse email valide
        private String email;
        private boolean enabled;
        @Min(1) // Doit être au moins 1
        private int retryAttempts;
        // ... Getters/Setters
    }
    // ... Getters/Setters
}

Alternative : liaison par constructeur

Au lieu d'utiliser des setters pour la liaison, vous pouvez opter pour la liaison par constructeur. Cela est souvent préféré car cela permet de rendre les champs de la classe de propriétés final, garantissant ainsi l'immutabilité de l'objet de configuration une fois créé. Pour cela, fournissez un constructeur qui accepte toutes les propriétés comme arguments.

Depuis Spring Boot 2.2, si votre classe est activée uniquement via @EnableConfigurationProperties ou @ConfigurationPropertiesScan (et n'est pas aussi un bean scanné via @Component), vous n'avez généralement plus besoin de l'annotation @ConstructorBinding. Cependant, son ajout explicite peut clarifier l'intention.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.context.properties.ConstructorBinding; // Peut être nécessaire ou implicite

@ConfigurationProperties(prefix = "app")
// @ConstructorBinding // Explicite si nécessaire (par ex. si aussi @Component)
public class ImmutableAppProperties {

    private final String name;
    private final String version;
    private final Notifications notifications;

    // Le constructeur prend toutes les valeurs
    public ImmutableAppProperties(String name, @DefaultValue("1.0") String version, Notifications notifications) {
        this.name = name;
        this.version = version;
        this.notifications = notifications; // L'objet Notifications doit aussi utiliser la liaison constructeur si immuable
    }

    // Uniquement des Getters (pas de Setters)
    public String getName() { return name; }
    public String getVersion() { return version; }
    public Notifications getNotifications() { return notifications; }

    // Classe interne immuable (exemple)
    public static class Notifications {
        private final String email;
        private final boolean enabled;
        private final int retryAttempts;

        public Notifications(String email, @DefaultValue("true") boolean enabled, @DefaultValue("3") int retryAttempts) {
            this.email = email;
            this.enabled = enabled;
            this.retryAttempts = retryAttempts;
        }
        // Uniquement Getters...
        public String getEmail() { return email; }
        public boolean isEnabled() { return enabled; }
        public int getRetryAttempts() { return retryAttempts; }
    }
}

// Note: @DefaultValue peut être utilisé sur les paramètres du constructeur pour les valeurs par défaut.

Cette approche est particulièrement adaptée si vous utilisez des Records Java (depuis Java 16), qui sont immuables par nature et se prêtent parfaitement à la liaison par constructeur.

Conclusion : la voie royale pour la configuration structurée

En résumé, @ConfigurationProperties est l'outil de choix dans Spring Boot pour gérer des ensembles de propriétés de configuration de manière organisée, robuste et maintenable. En liant directement les valeurs de configuration à des objets Java type-safe, vous bénéficiez d'une meilleure structure, d'une validation intégrée, d'une plus grande facilité de refactoring et d'une meilleure lisibilité du code par rapport à l'utilisation répétée de @Value. C'est la méthode recommandée pour toute configuration allant au-delà de quelques paramètres simples et isolés.