Mit dem JSR-303 “Bean Validation” hat Java seit Java EE Version 6 ein mächtiges und Schichten übegreifendes Mittel an die Hand bekommen, um komfortabel schon zur Entwurfszeit bekannte Constraints per Annotation auf Java-Beans zu legen. Die Java-Bean “weiß” damit also selbst, welche Daten sie annehmen darf oder nicht.
Die Referenzimplementierung zum JSR-303 kommt nicht verwunderlich von Hibernate, legten doch die Hibernate Validations (urprünglich mal nur auf Entities bezogen) den Grundstein für die Standardisierung.
Da der JSR-303 mit Java EE 6 eingeführt wurde, ist er natürlich Bestandteil aller zertifizierten Java EE 6 Application Server wie JBoss, Geronimo und weitere. Erfreulicherweise lässt sich der JSR-303 aber auch im Java SE Umfeld mit wenig Aufwand einsetzen und bietet auch hier allen Komfort und Flexibilität.
Folgendes Beispiel demonstriert de Einsatz des JSR-303 im Java SE Umfeld und zeigt danach, wie eigene Annotationen und Validatoren erstellt werden können.
javax.validation
validation-api
1.0.0.GA
org.hibernate
hibernate-validator
4.3.0.Final
Wer mit Maven arbeitet, braucht die oben genannten Dependencies. Die erste importiert die aktuelle API-Version des JSR-303, die zweite die konkrete Implementierung von Hibernate. Die Application Server liefern beide Bibliotheken schon mit, der Rest muss sich diese aus dem Internet besorgen.
public class MysteryObject {
@Min( value = 3)
@Max( value = 6)
private int uselessNumber;
public void setUselessNumber(int number) {
uselessNumber = number;
}
public int getUselessNumber() {
return uselessNumber;
}
}
Hier eine konkrete und unglaublich nützliche Klasse, die genauso nützliche Validierungen enthält. Der JSR-303 liefert umfangreiche Annotationen und Validatoren mit, die die meisten Standardfälle abdecken. Dazu gehören z.B. Min
, Max
für Zahlen, Length
für Strings und allgemeines wie NotNull
. Die Hibernate Implementierung bringt zusätzliche Validatoren mit, die man aber nur einsetzen sollte, wenn man sich im Klaren darüber ist, dass man damit den Standard verlässt. (Ähnlich wie beim Einsatz von der JPA API und Hibernate). Einen Überblick über alle möglichen (Standard) Annotationen findet man in den JavaDocs der API.
Obige Validierungen sollen demnach verhindern, dass die nützliche Zahl Werte kleiner drei und größer sechs enthalten darf.
public class Main {
public int main(String[] args) {
MysteryObject foo = new MysteryObject();
foo.setUselessNumber(42);
System.out.println("Die Antwort auf alle Fragen lautet " + foo.getUselessNumber());
}
}
Dieses kleine Programm stellt syntaktisch natürlich validen Code dar. Semantisch sollen die Validierungen der Java-Bean aber bewirken, dass das Objekt nicht den Wert 42 annehmen dürfte. Führt man obiges Beispiel aus, liefert es aber die korrekte Antwort auf alle Fragen. Während sich der Application Server in vielen Fällen selbst darum kümmert, wann die Java-Beans validiert werden, muss man sich im Java SE Umfeld selbst darum kümmern und das Validieren explizit durchführen.
public class Main {
public int main(String[] args) {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
MysteryObject foo = new MysteryObject();
foo.setUselessNumber(42);
// Validiere das Objekt, bevor wir es weiter vearbeiten
Set> violations = validator.validate(foo);
if (!violations.isEmpty()) {
System.out.println("Ihr Objekt enthält ein oder mehrere invalide Daten!");
exit(0);
}
System.out.println("Die Antwort auf alle Fragen lautet " + foo.getUselessNumber());
}
}
Beim Progmmstart werden nun erstmal alle Vorbereitungen zur Validierung getroffen und dann, bevor das konkrete Objekt weiter vearbeitet wird, die Validierung durchgeführt. Die validate
Methode liefert im Fehlerfall eine Liste aller Violations im überprüften Objekt. Diese lassen sich sinnvoller Weise an zentraler Stelle auswerten und den Benutzer informieren. Im Beispiel wird das Programm einfach beendet.
Mehr ist in der Tat nicht nötig um eine einfache Bean Validierung durchzuführen. Da die vorhandenen Validatoren aber nicht für alle Anwendungsfälle ausreichend sind, lassen sich natürlich beliebig eigene Annotationen und Validatoren definieren. Für das Beispiel soll eine Annotation samt Validator implementiert werden, die auf eine Liste mit erlaubten Werten prüft.
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = AllowedValidator.class)
@Documented
public @interface Allowed {
String message() default "Value not in set of allowed values!";
int[] value() default {};
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
Bei der Annotation handelt es sich um eine ganz normale Java Annotation. Um diese als Constraint nutzen zu können, muss sie zwingend die Felder message
, groups
und payload
enthalten. Das Feld value
enthält die Liste der erlaubten Werte, auf die geprüft werden soll. Weiterhin muss mit der Annotation @Constraint
die tatsächliche Validator-Klasse angegeben werden.
public class AllowedValidator implements ConstraintValidator {
Set allowedValues;
@Override
public void initialize(Allowed annotation) {
allowedValues = new HashSet<>();
for (int value : annotation.value()) {
allowedValues.add(value);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return allowedValues.contains(value);
}
}
Der passende Validator implementiert das Interface ConstraintValidator
für die Annotation Allowed
an einem Attribut vom Typ Integer
. Der restliche Code sollte selbsterklärend sein.
public class MysteryObject {
@Allowed({ 0, 8, 15, 4711 })
private int uselessNumber;
public void setUselessNumber(int number) {
uselessNumber = number;
}
public int getUselessNumber() {
return uselessNumber;
}
}
Hier nun die eigene Annotation im Einsatz. Die nützliche Nummer darf nun nur noch Werte annehmen, die in der Annotation angegeben sind.
Damit ist der Grundstein für umfangreiche Validierungen sowohl in kleinen, als auch in großen Projekten gelegt. Bean Validation verhindert erfolgreich unnötige und unübersichtliche Prüfungen von Attribut-Werten an vielen unterschiedlichen Stellen im Quelltext. Mit Bean Validation und einem Validator an zentraler Stelle kann die eigentliche Business-Logik voraussetzen, dass ein Objekt nur valide Werte enthalten kann. Ständige Prüfungen auf null
entfallen. Daraus ergeben sich sehr viele Vorteile, die auf der Hand liegen sollten.