Utilisation du conditionnel au printemps

Dans cet article, je veux décrire une annotation conditionnelle très utile et souvent utilisée et l'interface Condition .


Le contexte de printemps est un énorme conteneur de haricots divers, à la fois au printemps lui-même et sur mesure. Vous voulez toujours avoir des outils de gestion flexibles pour ce zoo bin. L' annotation @Conditional est juste pour cela.


La façon la plus courante de gérer le contexte printanier consiste à utiliser des profils. Ils vous permettent de contrôler rapidement et facilement la création de beans. Mais parfois, un réglage plus fin peut être nécessaire.


Par exemple, lors des tests, un problème survient: un test unitaire sur la machine du développeur nécessite un bin de type X pour son travail, lors de l'exécution du même test sur le serveur de build, Y-bin est requis et Z-bin est requis en production. @Conditional propose une solution simple et facile dans ce cas la décision. Tout comme cela se produit souvent lorsque vous travaillez dans des équipes non synchronisées, quelqu'un n'a pas le temps de terminer sa révision avant la date limite et votre fonctionnalité est déjà prête. Il faut s'adapter à ces conditions et changer le comportement. En d'autres termes, ajoutez la possibilité de modifier le contexte de l'application sans recompiler, par exemple, en ne modifiant qu'un seul paramètre dans la configuration.


Considérez cette annotation plus en détail. Au-dessus de chaque bac du code source, nous pouvons ajouter @Conditional et le ressort vérifiera automatiquement les conditions spécifiées dans cette annotation lors de sa création.


Dans la documentation officielle, il est déclaré comme ceci:


@Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Documented public @interface Conditional 

Dans ce cas, vous devez y transférer un ensemble de conditions:


 Class<? extends Condition>[] 

Où conditionnel est l'interface fonctionnelle qui contient la méthode


 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) 

Vérifions comment cela fonctionne dans la pratique, en utilisant un exemple vivant. Notre application a des interfaces à la fois sous forme de services soap / rest - et sous forme de JMS. Mais les administrateurs n'ont pas eu le temps de préparer l'infrastructure appropriée à temps - nous ne pouvons pas utiliser JMS.
Il y a une configuration java pour JMS dans notre projet:


 @Configuration public class JmsConfig { ... } 

Spring trouve cette configuration et commence à l'initialiser. Ensuite, tous les autres beans dépendants sont extraits, par exemple, en lisant dans la file d'attente. Pour désactiver la création de cette configuration, nous utiliserons l'annotation dérivée conditionnelle - ConditioanalOnProperty


 @ConditionalOnProperty( value="project.mq.enabled", matchIfMissing = false) @Configuration public class JmsConfig { ... } 

Ici, nous indiquons le contexte au générateur que nous créons cette configuration uniquement s'il existe une valeur positive de la constante project.mq.enabled dans le fichier de paramètres.
Passons maintenant aux beans dépendants et marquons-les avec l'annotation ConditioanalOnBean , ce qui empêchera le ressort de créer des beans qui dépendent de notre configuration.


 @ConditionalOnBean(JmsConfig.class) @Component public class JmsConsumer { ... } 

Ainsi, avec un seul paramètre, nous pouvons désactiver les composants de l'application dont nous n'avons pas besoin, puis les ajouter au contexte en modifiant la configuration.


Avec le framework, il existe un grand nombre d'annotations prêtes à l'emploi couvrant 99% des besoins du développeur (qui seront décrites plus loin dans l'article). Mais que faire si vous devez gérer une situation spécifique. Pour ce faire, vous pouvez ajouter votre propre logique personnalisée au ressort.


Supposons que nous ayons un bean - SuperDBLogger , que nous voulons créer uniquement s'il y a une annotation @Loggable sur l'un de nos bins . A quoi cela ressemblera dans le code:


 @Component @ConditionalOnLoggableAnnotation public class SuperDBLogger 

Examinez le fonctionnement de l' annotation @ConditionalOnLoggableAnnotation :


 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(OnLoggableAnnotation.class) public @interface ConditionalOnLoggableAnnotation { } 

Nous n'avons plus besoin de paramètres, passons maintenant à la logique elle-même - le contenu de la classe OnLoggableAnnotation . Dans ce document, nous redéfinissons la méthode des correspondances , dans laquelle nous implémentons la recherche de beans marqués dans notre package.


 public class OnLoggableAnnotation implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ClassPathScanner scanner = new ClassPathScanner(); scanner.addIncludeFilter(new AnnotationTypeFilter(Loggable.class)); Set<BeanDefinition> bd = scanner.findInPackage("ru.habr.mybeans"); if (!bd.isEmpty()) return true; return false; } } 

Ainsi, nous avons créé une règle selon laquelle Spring crée désormais SuperDBLogger . Pour les passionnés de SpringBoot, les créateurs du framework ont ​​créé SpringBootCondition , qui succède à Condition . Il diffère par la signature de la méthode redéfinie:


 public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata); 

Autrement dit, en plus de la réponse, nous avons besoin d'un bac ou non, vous pouvez ajouter un message, qui peut ensuite être vu dans les journaux de printemps, comprendre pourquoi le bac a été créé ou non.


Pour créer des conditions plus complexes, différentes conditions peuvent être combinées; ces mécanismes fournissent les classes AnyNestedCondition, AllNestedConditions et NoneNestedConditions . Supposons que nous voulons créer deux conditions de sorte que lorsque l'une d'elles est exécutée, notre bean soit créé. Pour ce faire, créez votre propre classe et héritez-la d' AnyNestedCondition .


 public class AnnotationAndPropertyCondition extends AnyNestedCondition { public AnnotationAndPropertyCondition() { super(REGISTER_BEAN); } @ConditionalOnProperty(value = "db.superLogger") static class Condition1 {} @ConditionalOnLoggableAnnotation static class Condition2 {} } 

La classe n'a pas besoin d'être marquée en plus d'annotations; le ressort lui-même la trouvera et la traitera correctement. L'utilisateur n'a qu'à indiquer à quel stade de la configuration les conditions sont remplies: ConfigurationPhase .REGISTER_BEAN - lors de la création de beans réguliers, ConfigurationPhase .PARSE_CONFIGURATION - lorsqu'il travaille avec des configurations (c'est-à-dire, pour des compartiments marqués avec l'annotation @Configuration ).


De même, pour les classes AllNestedConditions et NoneNestedConditions , la première surveille toutes les conditions, la seconde garantit qu'aucune condition n'est remplie.


De plus, afin de vérifier plusieurs conditions, vous pouvez passer plusieurs classes avec conditions à @Conditional . Par exemple, @Conditional ({OnLoggableAnnotation.class, AnnotationAndPropertyCondition.class}) . Les deux doivent retourner true pour que la condition soit satisfaite et que le bean soit créé.


Comme je l'ai mentionné ci-dessus, il existe déjà de nombreuses solutions toutes faites avec ressort, qui sont présentées dans le tableau ci-dessous.


AnnotationLa description
ConditionalOnBeanLa condition est remplie si le bean souhaité est présent dans BeanFactory.
ConditionalOnClassLa condition est satisfaite si la classe requise se trouve dans le chemin de classe.
ConditionalOnCloudPlatformLa condition est remplie lorsqu'une plateforme spécifique est active.
ConditionalOnExpressionLa condition est vraie lorsque l'expression SpEL renvoie une valeur positive.
ConditionalOnJavaLa condition est remplie lorsque l'application est lancée avec une version spécifique de la JVM.
ConditionalOnJndiLa condition n'est remplie que si une ressource spécifique est disponible via JNDI.
ConditionalOnMissingBeanLa condition est remplie si le bean souhaité manque dans BeanFactory.
ConditionalOnMissingClassLa condition est vraie si la classe requise n'est pas dans le chemin de classe.
ConditionalOnNotWebApplicationLa condition est vraie si le contexte d'application n'est pas un contexte Web.
ConditionalOnPropertyLa condition est satisfaite si les paramètres nécessaires sont spécifiés dans le fichier de paramètres.
ConditionalOnResourceLa condition est satisfaite si la ressource souhaitée est présente dans le chemin de classe.
ConditionalOnSingleCandidateLa condition est remplie si le bean de la classe spécifiée est déjà contenu dans BeanFactory et qu'il est le seul.
ConditionalOnWebApplicationLa condition est vraie si le contexte d'application est un contexte Web.

Tous peuvent être appliqués ensemble sur une même définition d'un bean.


Ainsi, @Conditional est un outil de configuration de contexte assez puissant, rendant les applications encore plus flexibles. Mais il convient de considérer le fait que vous devez utiliser cette annotation avec précaution, car le comportement du contexte ne devient pas aussi évident que lors de l'utilisation de profils - avec un grand nombre de bacs configurés, vous pouvez rapidement vous perdre. Il est conseillé de documenter soigneusement et d'enregistrer son application dans votre projet, sinon le support du code entraînera des difficultés.

Source: https://habr.com/ru/post/fr462541/


All Articles