Validierung von Inputparametern
Annotation
Beans können mit verschiedenen Annotationen versehen werden, die für die Validierung genutzt werden. Beispiele sind @Valid
, @NotNull
, @Null
, @Size
oder @Pattern
Man kann auch eigene Annotationen erstellen, um einen eigenen Validator zu verwenden. Ein Beispiel folgt weiter unten.
Integration
Am besten erstellt man einen ValidationService und bindet diesen ein über
@Inject
private ValidationService validationService;
..
validationService.validate(zuValidierendesObjekt);
Im Service passiert die Validierung über:
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> violations = validator.validate(entity);
Groups
Ein Objekt kann unterschiedlich validiert werden. Wenn z.B. für Verträge für eine Vertragsart ein Attribut gesetzt werden soll und für eine andere Vertragsart nicht, können Gruppen verwendet werden, um dies umzusetzen. Dafür werden beim Aufruf der Validierung die Gruppen mitgegeben:
validator.validate(entity, groups);
Die Gruppen selbst sind ein einfaches Interface, das an den Beans in die Annotation der Validierung geschrieben werden.
public interface VertragTyp1 {}
public interface VertragTyp2 {}
@NotNull(groups = VertragTyp1.class)
@Null(groups = VertragTyp2.class)
private String variablesAttribut;
Custom Validator
Die Validierung kann auch komplexer werden. Z.B. könnnen von einem Enum nur bestimmte Werte zulässig sein, wenn ein anderes Attribut gesetzt ist. Dann macht es Sinn, einen eigenen Validator zu erstellen.
Für die Verwendung eines eigenen Validators wird eine Annotation erstellt, die an den Beans verwendet wird:
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
@Documented
public @interface ValidCustom {
String eigenerParameter() default "";
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@ValidCustom(eigenerParameter = "blub")
public class ZuValidierendeKlasse {
..
}
Über die Annotation @Target
lässt sich konfigurieren, an welchen Stellen die Annotation verwendet werden darf: Klasse, Feld, Parameter etc.
Der Validator sieht dann wie folgt aus:
public class CustomValidator implements ConstraintValidator<ValidCustom, BeanKlasse> {
String eigenerParameter;
@Override
public void initialize(ValidCustom contraintAnnotation) {
eigenerParameter = constraintAnnotation.eigenerParameter();
}
@Override
public boolean isValid(BeanKlasse zuValidierendesObjekt, ConstraintValidatorContext context) {
// hier sollte irgendwas validiert werden
boolean isValid = buildViolation(context, "field", "super tolle Fehlermeldung", () -> true);
return isValid;
}
private boolean buildViolation(ContraintValidatorContext context, String propertyNode, String msg, Supplier<Boolean> check) {
if(!check.get()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(msg).addPropertyNode(propertyNode).addConstraintViolation();
return false;
}
return true;
}
}
UnitTest
Testen lässt es sich in einem JUnit Test genau wie die Implementierung des Validierungsservice. Zum Beispiel, dass keine Fehler auftreten:
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
BeanKlasse zuValidierendesObjekt = ...
Set<ConstraintViolation<T>> violations = validator.validate(zuValidierendesObjekt);
assertTrue(violations.isEmpty());
Um zu testen, dass ein Fehler angezeigt wird, kann man durch alle Violations iterieren und nach einer bestimmten Violation anhand des PropertyPath oder der Message suchen.
Komplexere Validierungen
Beispiel für eine Validierung, die sich selbst als Liste verwendet:
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CustomValidator.class)
@Repeatable(ValidCustom.List.class)
@Documented
public @interface ValidCustom {
String typ() default "";
String validValue() default = "";
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
ValidCustom[] value();
}
}
Verwendung an der Klasse:
@ValidCustom.List({
@ValidCustom(typ = "blub", validValue = "blub"),
@ValidCustom(eigenerParameter = "tada", validValue = "bla")
})
public class SuperTolleKlasse {}