Contactez-nous

Comprendre les configurations conditionnelles (`@ConditionalOn...`)

Apprenez à utiliser les annotations @ConditionalOn... pour activer ou désactiver des configurations Spring Boot selon diverses conditions (classpath, beans, propriétés).

Le pouvoir de la conditionnalité dans la configuration

Au coeur de la flexibilité de l'auto-configuration de Spring Boot se trouve le mécanisme de configuration conditionnelle. Il permet à Spring de décider, au moment de la création du contexte d'application, si une configuration spécifique (une classe annotée @Configuration) ou un bean individuel (une méthode annotée @Bean) doit être chargé et traité ou complètement ignoré.

Cette approche est essentielle car elle permet à Spring Boot de s'adapter dynamiquement à l'environnement de l'application. Par exemple, une configuration liée à une base de données ne devrait s'activer que si les bibliothèques JDBC nécessaires sont présentes, ou une configuration par défaut ne devrait s'appliquer que si l'utilisateur n'a pas fourni sa propre version personnalisée.

La base de ce mécanisme est la méta-annotation @Conditional. Toutes les annotations @ConditionalOn... que nous allons explorer sont elles-mêmes annotées avec @Conditional, en spécifiant une implémentation concrète de l'interface Condition qui évalue la condition spécifique.

Panorama des annotations `@ConditionalOn...` courantes

Spring Boot fournit une riche collection d'annotations conditionnelles prêtes à l'emploi pour couvrir les scénarios les plus fréquents. Voici les plus importantes :

  • @ConditionalOnClass : La condition est remplie si toutes les classes spécifiées sont présentes dans le classpath. Très utile pour activer une configuration uniquement si une bibliothèque tierce est disponible.
    @Configuration
    @ConditionalOnClass(javax.sql.DataSource.class)
    class DataSourceRelatedConfig { // ... }
    
  • @ConditionalOnMissingClass : L'inverse de la précédente. La condition est remplie si aucune des classes spécifiées n'est présente dans le classpath.
    @Configuration
    @ConditionalOnMissingClass("com.fasterxml.jackson.databind.ObjectMapper")
    class AlternativeJsonProcessingConfig { // ... }
    
  • @ConditionalOnBean : La condition est remplie si un bean du type ou du nom spécifié existe déjà dans le contexte d'application (Bean Factory). Utile pour créer des beans qui dépendent d'autres beans déjà configurés.
    @Bean
    @ConditionalOnBean(DataSource.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
  • @ConditionalOnMissingBean : La condition est remplie si aucun bean du type ou du nom spécifié n'existe dans le contexte. C'est l'annotation la plus utilisée dans l'auto-configuration pour fournir des beans par défaut qui peuvent être surchargés par l'utilisateur.
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new DefaultMyServiceImpl(); // Fourni seulement si l'utilisateur n'a pas défini son propre MyService
    }
    
  • @ConditionalOnProperty : La condition est remplie si une propriété spécifique existe dans l'environnement Spring (fichiers properties/yml, variables d'environnement, etc.) et correspond éventuellement à une valeur donnée. Très puissant pour activer/désactiver des fonctionnalités via la configuration.
    // S'active si myapp.feature.enabled=true
    @Configuration
    @ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")
    class FeatureConfig { // ... }
    
    // S'active si myapp.feature.mode existe (peu importe sa valeur)
    @Bean
    @ConditionalOnProperty(name = "myapp.feature.mode", matchIfMissing = false) 
    MyModeBean createModeBean() { // ... }
    
  • @ConditionalOnResource : La condition est remplie si une ressource spécifiée (fichier, etc.) peut être trouvée dans le classpath.
    @Configuration
    @ConditionalOnResource(resources = "classpath:custom-config.xml")
    class CustomXmlConfigLoader { // ... }
    
  • @ConditionalOnWebApplication : La condition est remplie si l'application est considérée comme une application web (par exemple, si Spring MVC est sur le classpath et qu'un WebApplicationContext est utilisé).
    @Configuration
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    class WebSpecificConfig { // ... }
    
  • @ConditionalOnNotWebApplication : La condition est remplie si l'application n'est pas une application web.
    @Configuration
    @ConditionalOnNotWebApplication
    class CommandLineSpecificConfig { // ... }
    
  • @ConditionalOnExpression : La condition est remplie si une expression SpEL (Spring Expression Language) donnée évalue à true. Offre une flexibilité maximale pour des conditions complexes.
    @Bean
    @ConditionalOnExpression("${myapp.security.enabled:true} and ${myapp.mode} == 'production'")
    SecurityFilter securityFilter() { // ... }
    

Combiner les conditions et la logique booléenne

Il est possible d'appliquer plusieurs annotations @ConditionalOn... sur une même classe ou méthode. Par défaut, toutes les conditions doivent être remplies pour que la configuration ou le bean soit activé (logique ET implicite).

Par exemple, pour n'activer une configuration que si la classe DataSource est présente ET qu'aucun bean DataSource n'a déjà été défini :

@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DefaultDataSourceConfiguration {
    // ... configuration pour créer un DataSource par défaut
}

Si vous avez besoin d'une logique OU, ou de combinaisons plus complexes, vous pouvez créer des classes de condition imbriquées et utiliser @Conditional(AnyNestedCondition.class) (pour OU) ou créer votre propre implémentation de SpringBootCondition.

Créer ses propres conditions personnalisées

Bien que les conditions fournies par Spring Boot couvrent la plupart des cas, vous pouvez avoir besoin de logiques de conditionnalité très spécifiques à votre application. Dans ce cas, vous pouvez créer votre propre condition en implémentant l'interface org.springframework.context.annotation.Condition.

Cette interface a une seule méthode : matches(ConditionContext context, AnnotatedTypeMetadata metadata). Vous y implémentez votre logique pour retourner true si la condition est remplie, false sinon. Le ConditionContext vous donne accès au BeanFactory, à l'environnement, au ClassLoader, etc.

Ensuite, vous utilisez votre condition personnalisée avec l'annotation @Conditional :

public class MyCustomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Votre logique complexe ici...
        boolean conditionMet = // ...
        return conditionMet;
    }
}

@Configuration
@Conditional(MyCustomCondition.class)
public class MyConditionalConfiguration {
    // ...
}

Comprendre et savoir utiliser ces annotations conditionnelles est crucial non seulement pour comprendre comment fonctionne l'auto-configuration de Spring Boot, mais aussi pour créer vos propres configurations réutilisables et adaptatives ("starters" personnalisés ou configurations partagées).