In diesem Artikel möchte ich eine sehr nützliche und häufig verwendete Schnittstelle für bedingte Anmerkungen und Bedingungen beschreiben .
Der Frühlingskontext ist ein riesiger Behälter mit verschiedenen Bohnen, sowohl dem Frühling selbst als auch den benutzerdefinierten. Sie möchten immer flexible Verwaltungstools für diesen Bin Zoo haben. Die @ Bedingte Annotation wird gerade dafür erstellt.
Die häufigste Methode zum Verwalten des Frühlingskontexts sind Profile. Mit ihnen können Sie die Erstellung von Bohnen schnell und einfach steuern. Manchmal ist jedoch eine genauere Abstimmung erforderlich.
Während des Testens tritt beispielsweise ein Problem auf: Für einen Komponententest auf dem Computer des Entwicklers ist ein X-Typ-Bin für seine Arbeit erforderlich. Wenn derselbe Test auf dem Build-Server ausgeführt wird, ist ein Y-Bin und ein Z-Bin für die Produktion erforderlich. @Conditional bietet in diesem Fall eine einfache und einfache Möglichkeit die Entscheidung. Genau wie so oft, wenn Sie in nicht synchronisierten Teams arbeiten, hat jemand keine Zeit, seine Überarbeitung fristgerecht abzuschließen, und Ihre Funktionalität ist bereits bereit. Es ist notwendig, sich an diese Bedingungen anzupassen und das Verhalten zu ändern. Fügen Sie also die Möglichkeit hinzu, den Anwendungskontext zu ändern, ohne ihn neu zu kompilieren, indem Sie beispielsweise nur einen Parameter in der Konfiguration ändern.
Betrachten Sie diese Anmerkung genauer. Über jedem Bin im Quellcode können wir @Conditional hinzufügen, und die Feder überprüft automatisch die in dieser Anmerkung angegebenen Bedingungen, wenn sie erstellt wird.
In der offiziellen Dokumentation heißt es folgendermaßen:
@Target(value={TYPE,METHOD}) @Retention(value=RUNTIME) @Documented public @interface Conditional
Gleichzeitig müssen Sie eine Reihe von Bedingungen darin übertragen:
Class<? extends Condition>[]
Wobei bedingt die funktionale Schnittstelle ist, die die Methode enthält
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
Lassen Sie uns anhand eines lebenden Beispiels überprüfen, wie es in der Praxis funktioniert. Unsere Anwendung verfügt über Schnittstellen sowohl in Form von Seifen- / Rest-Services als auch in Form von JMS. Die Administratoren hatten jedoch keine Zeit, die entsprechende Infrastruktur rechtzeitig vorzubereiten - wir können JMS nicht verwenden.
In unserem Projekt gibt es eine Java-Konfiguration für JMS:
@Configuration public class JmsConfig { ... }
Spring findet diese Konfiguration und beginnt mit der Initialisierung. Als nächstes werden alle anderen abhängigen Beans aufgerufen, z. B. aus der Warteschlange. Um die Erstellung dieser Konfiguration zu deaktivieren, verwenden wir die Annotation " Bedingte Ableitung" - "ConditioanalOnProperty"
@ConditionalOnProperty( value="project.mq.enabled", matchIfMissing = false) @Configuration public class JmsConfig { ... }
Hier teilen wir dem Builder den Kontext mit, dass wir diese Konfiguration nur erstellen, wenn die Einstellungsdatei einen positiven Wert der Konstante project.mq.enabled enthält.
Fahren wir nun mit den abhängigen Beans fort und markieren Sie sie mit der Annotation ConditioanalOnBean , die verhindert, dass der Frühling Beans erstellt, die von unserer Konfiguration abhängig sind.
@ConditionalOnBean(JmsConfig.class) @Component public class JmsConsumer { ... }
Mit einem einzigen Parameter können wir daher die Komponenten der Anwendung deaktivieren, die wir nicht benötigen, und sie dann durch Ändern der Konfiguration zum Kontext hinzufügen.
Zusammen mit dem Framework gibt es eine große Anzahl vorgefertigter Anmerkungen, die 99% der Anforderungen des Entwicklers abdecken (die später in diesem Artikel beschrieben werden). Aber was ist, wenn Sie mit einer bestimmten Situation umgehen müssen? Zu diesem Zweck können Sie der Feder Ihre eigene benutzerdefinierte Logik hinzufügen.
Angenommen, wir haben einen Bean - SuperDBLogger , den wir nur erstellen möchten, wenn über einem unserer Bins eine @ Loggable- Annotation vorhanden ist. Wie es im Code aussehen wird:
@Component @ConditionalOnLoggableAnnotation public class SuperDBLogger …
Überlegen Sie, wie die Annotation @ConditionalOnLoggableAnnotation funktioniert :
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(OnLoggableAnnotation.class) public @interface ConditionalOnLoggableAnnotation { }
Wir brauchen keine weiteren Parameter, jetzt gehen wir zur Logik selbst über - dem Inhalt der OnLoggableAnnotation- Klasse. Darin definieren wir die Übereinstimmungsmethode neu, bei der wir die Suche nach markierten Beans in unserem Paket implementieren.
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; } }
Daher haben wir eine Regel erstellt, nach der Spring jetzt SuperDBLogger erstellt. Für SpringBoot-Anhänger haben die Ersteller des Frameworks SpringBootCondition erstellt , den Nachfolger von Condition . Es unterscheidet sich in der Signatur der neu definierten Methode:
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
Das heißt, zusätzlich zur Antwort benötigen wir einen Papierkorb oder nicht. Sie können eine Nachricht hinzufügen, die dann in den Frühlingsprotokollen angezeigt wird, um zu verstehen, warum der Papierkorb erstellt wurde oder nicht.
Um komplexere Bedingungen zu erstellen, können verschiedene Bedingungen kombiniert werden. Diese Mechanismen stellen die Klassen AnyNestedCondition, AllNestedConditions und NoneNestedConditions bereit . Angenommen, wir möchten zwei Bedingungen erstellen, damit bei Ausführung einer davon unsere Bean erstellt wird. Erstellen Sie dazu Ihre eigene Klasse und erben Sie sie von AnyNestedCondition .
public class AnnotationAndPropertyCondition extends AnyNestedCondition { public AnnotationAndPropertyCondition() { super(REGISTER_BEAN); } @ConditionalOnProperty(value = "db.superLogger") static class Condition1 {} @ConditionalOnLoggableAnnotation static class Condition2 {} }
Die Klasse muss nicht zusätzlich mit Anmerkungen versehen werden, die Feder selbst findet sie und verarbeitet sie korrekt. Der Benutzer muss nur angeben, in welcher Phase der Konfiguration die Bedingungen erfüllt sind: ConfigurationPhase .REGISTER_BEAN - beim Erstellen regulärer Beans, ConfigurationPhase .PARSE_CONFIGURATION - beim Arbeiten mit Konfigurationen ( dh für mit @ Configuration Annotation gekennzeichnete Bins ).
In ähnlicher Weise überwacht für die Klassen AllNestedConditions und NoneNestedConditions die erste alle Bedingungen und die zweite stellt sicher, dass keine Bedingungen erfüllt sind.
Um mehrere Bedingungen zu überprüfen, können Sie auch mehrere Klassen mit Bedingungen an @Conditional übergeben . Zum Beispiel @Conditional ({OnLoggableAnnotation.class, AnnotationAndPropertyCondition.class}) . Beide müssen true zurückgeben, damit die Bedingung erfüllt ist und die Bean erstellt wird.
Wie oben erwähnt, gibt es bereits viele vorgefertigte Lösungen mit Feder, die in der folgenden Tabelle dargestellt sind.
Alle können zusammen auf eine Definition einer Bohne angewendet werden.
Daher ist @Conditional ein ziemlich leistungsfähiges Kontextkonfigurationstool , das Anwendungen noch flexibler macht. Es ist jedoch zu berücksichtigen, dass Sie diese Anmerkung sorgfältig verwenden müssen, da das Verhalten des Kontexts nicht so offensichtlich wird wie bei der Verwendung von Profilen. Bei einer großen Anzahl konfigurierter Bins können Sie schnell verwirrt werden. Es ist ratsam, die Anwendung in Ihrem Projekt sorgfältig zu dokumentieren und zu protokollieren, da sonst die Codeunterstützung zu Schwierigkeiten führt.