Java Spring : @Conditional

java spring conditional

En continuité de l’article sur les Beans, nous allons faire un focus sur les @Conditional !

@Conditional, c’est quoi ?

Pour bien commencer, une définition s’impose ! Qu’est-ce qu’un @Conditional ?
C’est une annotation qui permet de créer des conditions de création d’un Bean. Elle peut être mise sur une classe ou une méthode.

Dans cet article, nous allons voir plusieurs types de @Conditional, et les illustrer via des exemples.

Exemples

Avant de commencer, pour tous les exemples, nous allons utiliser un service qui sera une interface, que l’on va ensuite implémenter de différentes manières en utilisant des @Conditional.
Cette interface sera injectée dans un controller REST grâce à @Autowired.

Controller REST :

@RestController
@RequestMapping("/api")
public class SimpleResource {
        @Autowired
        TravaillerService mTravaillerService;

        @GetMapping("/work")
        String work() {
            return  mTravaillerService.doWork();
        }
}

Interface du service :

public interface TravaillerService {
    String doWork();
}

@ConditionalOnProperty

@ConditionalOnProperty vérifie si la propriété du context contient une valeur. Par défaut, la propriété demandée doit être présente, et différente de false.
On peut utiliser havingValue() et matchIfMissing() afin de personnaliser ce comportement.

Le chemin de la propriété est défini grâce à prefix() et value().

havingValue() est utilisé pour préciser la valeur que la propriété doit avoir.

Si la propriété n’existe pas dans le context, matchIfMissing() est utilisé.

Implémentation 1 :

@Service
@ConditionalOnProperty(prefix = "entreprise", name = "domaine.titre", havingValue = "developpeur")
public class DevelopperTravaillerServiceImpl implements TravaillerService {
    @Override
    public String doWork() {
        return "Coder";
    }
}

Implémentation 2 :

@Service
@ConditionalOnProperty(name = "entreprise.domaine.titre", havingValue = "graphiste")
public class GraphisteTravaillerServicerImpl implements TravaillerService {
    @Override
    public String doWork() {
        return "Dessiner";
    }
}

Fichier de properties :

entreprise.domaine.titre=graphiste

En lançant avec le fichier de properties ci-dessus, l’implémentation 2 va donc être injectée, et la réponse à /api/work sera « Dessiner ».

@ConditionalOnMissingBean 

Match uniquement quand aucun autre Bean spécifié n’a été instancié.

La condition peut seulement vérifier les Beans qui ont été traités jusqu’à maintenant. Si un autre Bean est susceptible d’être créé par une autre configuration, attention donc à l’utiliser en dernier.

Implémentation 3 :

@Service
@ConditionalOnMissingBean(value = {DevelopperTravaillerServiceImpl.class, GraphisteTravaillerServicerImpl.class})
public class DefautTravaillerServiceImpl implements TravaillerService {

    @Override
    public String doWork() {
       return "Dormir";
    }
}

Fichier de properties :

entreprise.domaine.titre=pianiste

En lançant avec le fichier de properties ci-dessus, aucun des 2 Beans Developper et Graphiste ne seront instanciés.
L’implémentation 3 va donc être injectée, et la réponse à /api/work sera «Dormir ».

@ConditionalOnBean 

Comportement inverse de @ConditionalOnMissingBean, match uniquement quand le ou tous les Beans sont présents.

@Conditional

Utilisé avec une ou des org.springframework.context.annotation.Conditions. Toutes les conditions doivent être bonnes pour que le @Conditional match.

Condition :

public class ArchitecteCondition implements Condition {
    @Override
    public boolean matches(ConditionContext pConditionContext, AnnotatedTypeMetadata pAnnotatedTypeMetadata) {
        return "architecte".equals(pConditionContext.getEnvironment().getProperty("entreprise.domaine.titre"));
    }
}

Implémentation 4 :

@Service
@Conditional(value = ArchitecteCondition.class)
public class ArchitecteTravaillerServiceImpl implements TravaillerService {
    @Override
    public String doWork() {
        return "Architecturer";
    }
}

Fichier de properties :

 entreprise.domaine.titre=architecte

Avec la configuration ci-dessus, nous aurons donc « Architecturer » lors de l’appel à l’API.

Attention, pour que ça fonctionne il faut tout de même ajouter ArchitecteTravaillerServiceImpl.class à l’implémentation 3.

Sinon, deux Beans voudront être injectés, et l’application ne pourra pas se lancer.

Il existe d’autres types de @Conditional, mais ceux-ci devraient être ceux qui vous serviront le plus.
A savoir qu’ils peuvent également être utilisés sur des méthodes, ce qui peut permettre de faire des configurations plus poussées.

Aller plus loin

Un exemple ci-dessous permettant de gérer simplement le service TravaillerService en fonction de 2 propriétés, et seulement 2 classes.

Configuration 1 :

/**
 * Configuration lorsque multitache est à false dans application.properties
 */
@Configuration
@ConditionalOnProperty(name = "entreprise.domaine.multitache", havingValue = "false")
public class TravaillerServiceConfiguration {

    @Bean(name = "doWork")
    @Conditional(value = ArchitecteCondition.class)
    public TravaillerService doWorkArchitecte() {
        return () -> "Architecturer";
    }

    @Bean(name = "doWork")
    @ConditionalOnProperty(name = "entreprise.domaine.titre", havingValue = "graphiste")
    public TravaillerService doWorkGraphist() {
        return () -> "Dessiner";
    }

    @Bean(name = "doWork")
    @ConditionalOnProperty(prefix = "entreprise", name = "domaine.titre", havingValue = "developpeur")
    public TravaillerService doWorkDevelopper() {
        return () -> "Coder";
    }
}

Configuration 2 :

/**
 * Configuration lorsque multitache est à true dans application.properties
 */
@Configuration
@ConditionalOnProperty(name = "entreprise.domaine.multitache", havingValue = "true")
public class TravaillerMultitacheServiceConfiguration {

    @Bean(name = "doWork")
    @Conditional(value = ArchitecteCondition.class)
    public TravaillerService doWorkArchitecte() {
        return () -> "Architecturer, et plein d'autres choses";
    }

    @Bean(name = "doWork")
    @ConditionalOnProperty(name = "entreprise.domaine.titre", havingValue = "graphiste")
    public TravaillerService doWorkGraphist() {
        return () -> "Dessiner, et plein d'autres choses";
    }

    @Bean(name = "doWork")
    @ConditionalOnProperty(prefix = "entreprise", name = "domaine.titre", havingValue = "developpeur")
    public TravaillerService doWorkDevelopper() {
        return () -> "Coder, et plein d'autres choses";
    }
}

Avec ces 2 classes, nous avons donc 8 Beans avec 8 possibilités de @Conditional : 4 lorsque entreprise.domaine.multitache est à true et 4 lorsqu’il est à false.

Fichier de properties :

entreprise.domaine.titre=architecte
entreprise.domaine.multitache=true

Avec le fichier de properties ci-dessus, nous aurons donc « Architecturer, et plein d’autres choses » en réponse à l’API.

Les @Conditional sont très puissants, et ne demandent qu’à être utilisés correctement, afin de faire des applications hautement paramétrables.

Arthur Combe