Gestion de la Conditionnalité d'Initialisation des Beans dans Spring Boot

Lors du démarrage d'une application basée sur Spring Boot, la méthode principal exécute une logique qui instancie un ConfigurableApplicationContext. Cette étape est cruciale car elle permet d'exposer l'environnement d'exécution et d'accéder aux objets bean créés par le framework via injection ou récupération explicite.


@SpringBootApplication
public class LauncherPrincipal {

    public static void main(String[] args) {
        // Lancement du contexte applicatif
        ConfigurableApplicationContext contexte = SpringApplication.run(LauncherPrincipal.class, args);
        
        // Récupération d'un composant par nom défini
        Object serviceExterne = contexte.getBean("nomDuBean");
        System.out.println(serviceExterne);
    }
}

Implémentation Manuelle d'une Condition

Pour contrôler dynamiquement si un Bean doit être enregistré dans le conteneur, Spring expose l'interface org.springframework.context.annotation.Condition. Elle définit une seule méthode : matches, qui retourne un booléen. Si cette méthode renvoie true, le bean est créé ; sinon, il est ignoré.

Voici l'interface de base :


public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Imaginons un scénario où nous désirons créer une entité utilisateur uniquement si certaines ressources spécifiques sont disponibles dans le classpath. Nous commençons par définir la classe POJO ciblée.


package demo.configuration;

public class ConfigurationModel {
    private String identifiant;
    
    public ConfigurationModel() {
        this.identifiant = "actif";
    }
    
    public String getIdentifiant() { return identifiant; }
}

Ensuite, nous codons la logique de vérification. Ici, nous simulons la détection de la présence d'une librairie externe (comme Redis) en vérifiant la classe correspondante via le ClassLoader.


package demo.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class DetecteurClasse implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Tentative de chargement de la classe cible depuis le classpath
        String nomClasseCible = "redis.clients.jedis.Jedis";
        try {
            Class> cls = Class.forName(nomClasseCible);
            return true; // La classe existe, la condition est satisfaite
        } catch (ClassNotFoundException e) {
            return false; // La classe manque, on annule le Bean
        }
    }
}

Cette logique peut ensuite être attachée à une configuration de Bean au sein d'une classe annotée avec @Configuration.


package demo.config;

import demo.condition.DetecteurClasse;
import demo.configuration.ConfigurationModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public defautsConfig {

    @Bean
    @Conditional(DetecteurClasse.class) 
    public ConfigurationModel modeleDeDonnees() {
        return new ConfigurationModel();
    }
}

Création d'une Annotation Métadonnées Personnalisée

Pour éviter de répéter l'instance de la classe Condition directement sur chaque méthode Bean, Spring permet de créer des annotations personnalisées. Ces dernières agissent comme des marqueurs simplifiés qui délèguent leur évaluation à une condition spécifique.

Nous allons créer une annotation @BesoinDeLibrairie qui utilise notre détecteur personnalisé.


package demo.condMeta;

import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;

// On spécifie que cette annotation s'applique aux méthodes et types
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DetecteurClasse.class) // La logique réelle reste ici

public @interface BesoinDeLibrairie {
    // Paramètres optionnels si nécessaire, par défaut vide pour cet exemple
}

L'utilisation de cette annotation simplifie considérablement la déclaration des beans :


package demo.config;

import demo.condMeta.BesoinDeLibrairie;
import demo.configuration.ConfigurationModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfiguration {

    // Remplace @Conditional par notre annotation personnalisée
    @Bean
    @BesoinDeLibrairie 
    public ConfigurationModel modeleDeDonnees() {
        return new ConfigurationModel();
    }
}

Dans ce cas, @BesoinDeLibrairie agit comme un décorateur pour la logique interne définie par DetecteurClasse. Cela maintient le code propre tout en conservant la puissance du système de conditionnement natif de Spring.

Annotations Conditionnelles Natifs Utilitaires

Au-delà des implémentations manuelles, Spring Boot fournit plusieurs annotations prédéfinies optimisées pour les cas d'usage courants :

  • @ConditionalOnProperty : Active le Bean uniquement si une propriété spécifique présente dans les fichiers de configuration possède une valeur donnée.
  • @ConditionalOnClass : Vérifie la présence d'une classe spécifiée dans le classpath avant d'instancier le Bean.
  • @ConditionalOnMissingBean : Enregistre le Bean seulement si aucune autre bean de même type n'existe déjà dans le contexte (prévention des conflits).

Étiquettes: spring-boot Java Annotations ioc-container conditional-instantiation

Publié le 18 août à 04h02