Zusammenkommen ist ein Beginn, zusammenbleiben ist ein Fortschritt, zusammenarbeiten ist ein Erfolg.
Henry Ford
Das Validieren in Java hat viele interessante aber selten benutze Möglichkeiten. In diesem Beitrag geht es um das Kombinieren der Validierungs-Annotationen. Ein zu prüfenden Datum mit verschiedenen Validierungen zu annotieren ist sicherlich bekannt.
@Min(42) @Max(42) private int theAnswer;
In diesem Beitrag wollen wir uns aber eine weniger bekannte Variante anschauen.
Zur Demonstration validieren wir einen Eintrag in der Klasse AncestorProperties
.
@ConfigurationProperties("ancestor") @Validated @Data public class AncestorProperties { private List<URI> gallerySources; }
Diese Klasse wird von Spring Boot beim Start automatisch mit Property-Werten befüllt. Dabei werden nur Properties beachtet, die mit “ancestor
.” beginnen. Die Annotationen @Validated
und @Data
dienen zur Validierung und zur Bereitstellung von Gettern und Settern.
In der Liste gallerySources
werden die Adressen von fiktiven Servern für Ahnenbildern konfiguriert. Dazu reicht eine Eintrag in unserer application.properties
Datei.
# property for ancestor gallery sites ancestor.gallerySources=https://ahnen-bilder.de,https://vorfahren-im-bild.org
Spring Boot sorgt dabei für das Zerschneiden der kommaseparierten Liste und der Konvertierung der Daten in URI
Instanzen. Fehlerhafte Adressen werden so schon beim Programmstart erkannt und führen zu einer Fehlermeldung.
In dieser Forn werden jedoch drei Dinge nicht erkannt, die wir aus didaktischen Gründen hier betrachten wollen.
- Die Liste darf nicht leer sein
- Die Adressen müssen alle HTTPS unterstützen
- Es darf keine Localhost Adresse sein
Mit den dazu nötigen Validierungs-Annotationen sieht unsere Klasse wie folgt aus.
@ConfigurationProperties("ancestor") @Validated @Data public class AncestorProperties { @NotEmpty private List<@NotNull @Https @NotLocalhost URI> gallerySources; }
Die Annotation @NotEmpty
meldet einen Fehler, wenn die Liste leer ist, Die Annotation
meldet einen Fehler, wenn ein NULL Eintrag in der Liste existiert. Die Annotation @NotNull
@Https
meldet einen Fehler, wenn das Schema der URI nicht HTTPS ist. Als letztes meldet die Annotationen @NotLocalhost
einen Fehler, wenn die Domäne Localhost ist.
Die Annotationen @Https
und @NotLocalhost
sind keine Standard Annotationen. Sie stammen aus dem Projekt URI Validator. Beide Annotationen sind auf die Typen String
, java.net.URL
und java.net.URI
anwendbar und beide Annotationen verwenden die Möglichkeit, neue Validierungs-Annotationen aus bestehenden zu komponieren.
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Constraint(validatedBy = {}) @Schema(regexp = "http") public @interface Http { String message() default "{de.schegge.validator.HTTP.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Die hier dargestellte Annotation @Https
beruht auf der Validierungs-Annotationen @Schema
, die in Zeile vier der Definition erscheint. Diese Annotation prüft, ob ein URI Schema auf ein angegebenes reguläres Muster passt. Die Annotation @Https
validiert also nicht selber, sondern ist nur eine praktische Kurzform, für eine häufig verwendete Variante.
Dieser Ansatz kann auch gewählt werden um die Validierung in der Beispiel-Klasse etwas kompakter zu schreiben. Die folgende Annotationen ersetzt die Annotationen @NotNull
, @Https
und @NotLocalhost
.
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Constraint(validatedBy = {}) @NotNull @Https @NotLocalhost public @interface SecureExternal { String message() default "keine sichere, externe Adresse"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Das Beispiel schaut am Ende wie folgt aus und ist durch die spezifischere Annotation auch einfacher zu verstehen.
@ConfigurationProperties("ancestor") @Validated @Data public class AncestorProperties { @NotEmpty private List<@SecureExternal URI> gallerySources; }
Aber es wäre schon enttäuschend, wenn da nicht noch etwas mehr möglich wäre. Vielleicht sollen die URL noch ein wenig mehr eingeschränkt werden? In dem Fall können wir nicht mehr die Annotation @NotLocalhost
verwenden, sondern müssen auf die Basis Annotation @Host
ausweichen. Damit wir jetzt nicht für jeden Anwendungsfall eine neue Annotation schreiben müssen, geben wir ein regulären Ausdruck direkt an.
@ConfigurationProperties("ancestor") @Validated @Data public class AncestorProperties { @NotEmpty private List<@SecureExternal(".*\\.de") URI> gallerySources; }
Nun muss aber unsere Annotation den Wert an die
Annotation weiterreichen, weil diese ja die Validierung durchführt. Dafür gibt es die Standard Annotation @Host
@OverridesAttributes
, deren Anwendung durch das folgende Beispiel klar wird.
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Constraint(validatedBy = {}) @NotNull @Https @NotLocalhost @Host(regexp=".*") public @interface SecureExternal { String message() default "keine sichere, externe Adresse"; @OverridesAttribute(constraint = Host.class, name = "regexp") String value() default ".*" Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Mit diesen Möglichkeiten kann man domänenspezifische Validierungs-Annotationen schreiben, die zur Verständlichkeit beitragen, aber immer noch eine Flexibilität bereitstellen.
Wer Interesse an dem URI Validator Projekt gefunden hat, ist eingeladen die Annotationen auszuprobieren und Feedback in Form von Lob, Kritik und Verbesserungsvorschlägen zu geben.
Nachtrag
Im Zuge der Ankündigung von JFrog, ihr Repository JCenter zu schließen, ist die Bibliothek als Java 11 Binary nach Maven Central migriert.
<dependency> <groupId>de.schegge</groupId> <artifactId>uri-validator</artifactId> <version>0.2.2</version> </dependency>
1 thought on “Validieren wie Nick Knatterton- kombinieren, kombinieren”
Comments are closed.