Frühlingsbohnen-Validierung
Die Datenvalidierung ist kein neues Thema in der Entwicklung von Webanwendungen.
Wir werfen einen kurzen Blick auf die Datenvalidierung im Java-Ökosystem im Allgemeinen und das Spring Framework im Besonderen. Die Java-Plattform war der De-facto-Standard für die Implementierung der Datenvalidierung das ist die Bean Validation-Spezifikation. Die Bean-Validierungsspezifikation hat mehrere Versionen:
- 1.0 (JSR-303),
- 1.1 (JSR-349),
- 2.0 (JSR 380) – die neueste Version

Diese Spezifikation definiert eine Reihe von Komponenten, Schnittstellen und Anmerkungen. Dies bietet eine Standardmethode, um Einschränkungen für die Parameter und Rückgabewerte von Methoden und Parametern von Konstruktoren festzulegen, API bereitzustellen, um Objekte und Objektgraphen zu validieren.
Ein deklaratives Modell wird verwendet, um Objekten und ihren Feldern Einschränkungen in Form von Anmerkungen aufzuerlegen. Es gibt vordefinierte Anmerkungen wie @NotNull
, @Digits
, @Pattern
, @Email
, @CreditCard
. Es besteht die Möglichkeit, neue benutzerdefinierte Einschränkungen zu erstellen.
Die Validierung kann manuell oder natürlicher ausgeführt werden, wenn andere Spezifikationen und Frameworks Daten zum richtigen Zeitpunkt validieren, z. B. Benutzereingaben, Einfügungen oder Aktualisierungen in JPA.
Validierung in Java-Beispiel
Sehen wir uns in diesem einfachen Bean-Validierungsbeispiel in einer regulären Java-Anwendung an, wie dies in der Praxis umgesetzt werden kann.
Wir haben ein Objekt, das wir validieren möchten, wobei alle Felder mit Einschränkungen versehen sind.
public class SimpleDto {
@Min(value = 1, message = "Id can't be less than 1 or bigger than 999999")
@Max(999999)
private int id;
@Size(max = 100)
private String name;
@NotNull
private Boolean active;
@NotNull
private Date createdDatetime;
@Pattern(regexp = "^asc|desc$")
private String order = "asc";
@ValidCategory(categoryType="simpleDto")
private String category;
…
Constructor, getters and setters
Jetzt können wir es in einer einfachen Java-Anwendung verwenden und das Objekt manuell validieren.
public class SimpleApplication {
public static void main(String[] args) {
final SimpleDto simpleDto = new SimpleDto();
simpleDto.setId(-1);
simpleDto.setName("Test Name");
simpleDto.setCategory("simple");
simpleDto.setActive(true);
simpleDto.setOrder("asc");
simpleDto.setCreatedDatetime(new Date());
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.usingContext().getValidator();
Set constrains = validator.validate(simpleDto);
for (ConstraintViolation constrain : constrains) {
System.out.println(
"[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]"
);
}
}
}
Und das Ergebnis in der Konsole lautet:
“[id] [Id can't be less than 1 or bigger than 999999]”
Validierungseinschränkung und Anmerkung
Erstellen Sie eine benutzerdefinierte Validierungseinschränkung und Anmerkung.
@Retention(RUNTIME)
@Target(FIELD)
@Constraint(validatedBy = {ValidCategoryValidator.class})
public @interface ValidCategory {
String categoryType();
String message() default "Category is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And constraint validation implementation:
public class ValidCategoryValidator implements ConstraintValidator<ValidCategory, String> {
private static final Map<String, List> availableCategories;
static {
availableCategories = new HashMap<>();
availableCategories.put("simpleDto", Arrays.asList("simple", "advanced"));
}
private String categoryType;
@Override
public void initialize(ValidCategory constraintAnnotation) {
this.setCategoryType(constraintAnnotation.categoryType());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
List categories = ValidCategoryValidator.availableCategories.get(categoryType);
if (categories == null || categories.isEmpty()) {
return false;
}
for (String category : categories) {
if (category.equals(value)) {
return true;
}
}
return false;
}
}
Im obigen Beispiel stammen die verfügbaren Kategorien aus einer einfachen Hash-Map. In realen Anwendungsfällen können sie aus der Datenbank oder anderen Diensten abgerufen werden.
Bitte beachten Sie, dass Einschränkungen und Validierungen nicht nur auf Feldebene, sondern auch für ein ganzes Objekt angegeben und durchgeführt werden können.
Wenn wir verschiedene Feldabhängigkeiten validieren müssen, z. B. das Startdatum, darf es nicht nach dem Enddatum liegen.
Die am häufigsten verwendeten Implementierungen der Bean-Validierung Spezifikationen sind Hibernate Validator und Apache BVal .
Validierung mit Spring
Das Spring-Framework bietet mehrere Funktionen für die Validierung.
- Unterstützung für Bean Validation API-Versionen 1.0, 1.1 (JSR-303, JSR-349) wurde in Spring Framework ab Version 3 eingeführt.
- Spring hat seine eigene Validator-Schnittstelle, die sehr einfach ist und in einer bestimmten DataBinder-Instanz eingestellt werden kann. Dies könnte nützlich sein, um eine Validierungslogik ohne Anmerkungen zu implementieren.
Bean-Validierung mit Spring
Spring Boot bietet eine gestartete Validierung, die in das Projekt aufgenommen werden kann:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Dieser Starter bietet eine Version von Hibernate Validator kompatibel mit dem aktuellen Spring Boot.
Mithilfe der Bean-Validierung könnten wir einen Anforderungstext, Abfrageparameter und Variablen innerhalb des Pfads validieren (z. B. / /simpledto/{id} ) oder beliebige Methoden- oder Konstruktorparameter.
POST- oder PUT-Anforderungen
In POST- oder PUT-Anforderungen übergeben wir beispielsweise die JSON-Nutzlast, Spring konvertiert sie automatisch in ein Java-Objekt und jetzt möchten wir das resultierende Objekt validieren. Lassen Sie uns SimpleDto
verwenden Objekt aus Beispiel 1:
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@RequestMapping(path = "", method = RequestMethod.POST, produces =
"application/json")
public SimpleDto createSimpleDto(
@Valid @RequestBody SimpleDto simpleDto) {
SimpleDto result = simpleDtoService.save(simpleDto);
return result;
}
}
Wir haben gerade @Valid
hinzugefügt Anmerkung zu SimpleDto
Parameter, der mit @RequestBody
kommentiert ist . Dadurch wird Spring angewiesen, die Validierung zu verarbeiten, bevor ein tatsächlicher Methodenaufruf durchgeführt wird. Falls die Validierung fehlschlägt, löst Spring eine MethodArgument NotValidException
aus die standardmäßig eine 400-Antwort (Bad Request) zurückgibt.
Validieren von Pfadvariablen
Das Validieren von Pfadvariablen funktioniert etwas anders. Das Problem ist, dass wir Constraint-Anmerkungen jetzt direkt zu Methodenparametern hinzufügen müssen, anstatt innerhalb von Objekten.
Damit dies funktioniert, gibt es 2 mögliche Optionen:
Option 1:@Validierte Anmerkung
Fügen Sie @Validated
hinzu Annotation an den Controller auf Klassenebene, um Constraint-Annotationen für Methodenparameter auszuwerten.
Option 2:Pfadvariable
Verwenden Sie ein Objekt, das die Pfadvariable darstellt, wie im folgenden Beispiel gezeigt:
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@RequestMapping(path = "/{simpleDtoId}", method = RequestMethod.GET, produces =
"application/json")
public SimpleDto getSimpleDto(
@Valid SimpleDtoIdParam simpleDtoIdParam) {
SimpleDto result = simpleDtoService.findById(simpleDtoIdParam.getSimpleDtoId());
if (result == null) {
throw new NotFoundException();
}
return result;
}
}
In diesem Fall haben wir SimpleDtoIdParam
Klasse, die simpleDtoId
enthält Feld, das anhand einer standardmäßigen oder benutzerdefinierten Bean-Einschränkungsanmerkung validiert wird. Name der Pfadvariablen (/{simpleDtoId} ) sollte mit dem Feldnamen identisch sein (damit Spring den Setter für dieses Feld finden kann).
private static final long serialVersionUID = -8165488655725668928L;
@Min(value = 1)
@Max(999999)
private int simpleDtoId;
public int getSimpleDtoId() {
return simpleDtoId;
}
public void setSimpleDtoId(int simpleDtoId) {
this.simpleDtoId = simpleDtoId;
}
}
Im Gegensatz zu Request Body Validierung, Pfadvariable Validierung löst ConstraintViolationException
aus anstelle von MethodArgumentNotValidException
. Daher müssen wir einen benutzerdefinierten Ausnahmehandler erstellen.
Das Java-Spring-Framework ermöglicht auch die Validierung von Parametern auf Serviceebene mit @Validated
Anmerkung (Klassenebene) und @Valid
(Parameterebene).
In Anbetracht der Tatsache, dass Spring JPA darunter Hibernate verwendet, unterstützt es auch die Bean-Validierung für Entitätsklassen. Bitte beachten Sie, dass es in vielen Fällen wahrscheinlich keine gute Idee ist, sich auf diese Validierungsebene zu verlassen, da dies bedeutet, dass die gesamte Logik zuvor mit ungültigen Objekten umgegangen ist.
Spring Validation Interface
Spring definiert eine eigene Schnittstelle für den Validierungs-Validator (org.springframework.validation.Validator). Es kann für eine bestimmte DataBinder-Instanz festgelegt werden und die Validierung ohne Anmerkungen implementieren (nicht deklarativer Ansatz).
Um diesen Ansatz zu implementieren, müssten wir:
- Implementieren Sie die Validator-Schnittstelle
- Validierer hinzufügen
Validator-Schnittstelle implementieren
Implementieren Sie die Validator-Schnittstelle, zum Beispiel können Sie mit unserem SimpleDto
arbeiten Klasse:
@Component
public class SpringSimpleDtoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return SimpleDto.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
if (errors.getErrorCount() == 0) {
SimpleDto param = (SimpleDto) target;
Date now = new Date();
if (param.getCreatedDatetime() == null) {
errors.reject("100",
"Create Date Time can't be null");
} else if (now.before(param.getCreatedDatetime())) {
errors.reject("101",
"Create Date Time can't be after current date time");
}
}
}
}
Überprüfen Sie hier, ob die erstellte Datetime
Zeitstempel liegt in der Zukunft.
Validator hinzufügen
Validator-Implementierung zu DataBinder
hinzufügen :
@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {
@Autowired
private SimpleDtoService simpleDtoService;
@Autowired
private SpringSimpleDtoValidator springSimpleDtoValidator;
@InitBinder("simpleDto")
public void initMerchantOnlyBinder(WebDataBinder binder) {
binder.addValidators(springSimpleDtoValidator);
}
@RequestMapping(path = "", method = RequestMethod.POST, produces =
"application/json")
public SimpleDto createSimpleDto(
@Valid @RequestBody SimpleDto simpleDto) {
SimpleDto result = simpleDtoService.save(simpleDto);
return result;
}
}
Jetzt haben wir SimpleDto
validiert mit Constraint-Annotationen und unserer benutzerdefinierten Spring Validation-Implementierung.