Ověření jarních fazolí
Ověřování dat není nové téma ve vývoji webových aplikací.
Krátce se podíváme na validaci dat v ekosystému Java obecně a konkrétně na Spring Framework. Platforma Java byla de facto standardem proimplementaci ověřování dat to je specifikace Bean Validation. Specifikace Bean Validation má několik verzí:
- 1.0 (JSR-303),
- 1.1 (JSR-349),
- 2.0 (JSR 380) – nejnovější verze

Tato specifikace definuje sadu komponent, rozhraní a anotací. To poskytuje standardní způsob, jak omezovat parametry a návratové hodnoty metod a parametrů konstruktorů, poskytovat API pro ověřování objektů a objektových grafů.
Deklarativní model se používá k umístění omezení ve formě anotací na objekty a jejich pole. Jsou zde předdefinované anotace jako @NotNull
, @Digits
, @Pattern
, @Email
, @CreditCard
. Existuje možnost vytvářet nová vlastní omezení.
Validace může probíhat ručně nebo přirozeněji, když jiná specifikace a rámce ověřují data ve správný čas, například zadáním uživatele, vložením nebo aktualizací v JPA.
Příklad ověření v jazyce Java
Pojďme se podívat na to, jak to lze provést v praxi v tomto jednoduchém příkladu Bean Validation v běžné Java aplikaci.
Máme objekt, který chceme ověřit se všemi poli anotovanými omezeními.
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
Nyní jej můžeme použít v jednoduché Java aplikaci a ručně ověřit objekt.
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() + "]"
);
}
}
}
A výsledek v Console bude:
“[id] [Id can't be less than 1 or bigger than 999999]”
Omezení ověření a anotace
Vytvořte vlastní ověřovací omezení a anotaci.
@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;
}
}
Ve výše uvedeném příkladu pocházejí dostupné kategorie z jednoduché hash mapy. V reálných případech použití aplikací je lze získat z databáze nebo jiných služeb.
Vezměte prosím na vědomí, že omezení a ověření lze specifikovat a provádět nejen na úrovni pole, ale také na celém objektu.
Když potřebujeme ověřit různé závislosti polí, například počáteční datum, nemůže to být po datu ukončení.
Nejpoužívanější implementace Bean Validation specifikace jsou Hibernate Validator a Apache BVal .
Ověření pomocí Spring
Framework Spring poskytuje několik funkcí pro ověření.
- Podpora pro Bean Validation API verze 1.0, 1.1 (JSR-303, JSR-349) byla zavedena ve Spring Frameworku počínaje verzí 3.
- Spring má vlastní rozhraní Validator, které je velmi základní a lze jej nastavit v konkrétní instanci DataBinder. To by mohlo být užitečné pro implementaci ověřovací logiky bez anotací.
Ověření fazolí pomocí jara
Spring Boot poskytuje spuštěné ověření, které lze zahrnout do projektu:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Tento startér poskytuje verzi Hibernate Validator kompatibilní s aktuálním Spring Boot.
Pomocí funkce Bean Validation bychom mohli ověřit tělo požadavku, parametry dotazu, proměnné v cestě (např. / /simpledto/{id} ), nebo jakákoli metoda nebo parametry konstruktoru.
Požadavky POST nebo PUT
V požadavcích POST nebo PUT například předáme JSON payload, Spring jej automaticky převede na Java objekt a nyní chceme výsledný objekt validovat. Použijme SimpleDto
objekt z 1 příkladu:
@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;
}
}
Právě jsme přidali @Valid
anotace k SimpleDto
parametr anotovaný @RequestBody
. Toto řekne Springu, aby zpracoval ověření před provedením skutečného volání metody. V případě, že se ověření nezdaří, Spring vyvolá MethodArgument NotValidException
která ve výchozím nastavení vrátí odpověď 400 (Bad Request).
Ověřování proměnných cesty
Ověřování proměnných cesty funguje trochu jinak. Problém je v tom, že nyní musíme přidávat anotace omezení přímo do parametrů metody místo do objektů.
Aby to fungovalo, existují 2 možné možnosti:
Možnost 1:@Validated Anotace
Přidejte @Validated
anotace do řadiče na úrovni třídy pro vyhodnocení omezujících anotací parametrů metody.
Možnost 2:Proměnná cesty
Použijte objekt, který představuje proměnnou path, jak je vidět v příkladu níže:
@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;
}
}
V tomto případě máme SimpleDtoIdParam
třída, která obsahuje simpleDtoId
pole, které bude ověřeno podle standardní nebo vlastní anotace omezení Bean. Název proměnné cesty (/{simpleDtoId} ) by měl být stejný jako název pole (takže Spring bude moci najít nastavovače pro toto pole).
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;
}
}
Na rozdíl od Tělo požadavku ověření, Proměnná cesty ověření vyvolá ConstraintViolationException
namísto MethodArgumentNotValidException
. Proto budeme muset vytvořit vlastní obslužnou rutinu výjimek.
Framework Java Spring také umožňuje ověřování parametrů na úrovni služeb pomocí @Validated
anotace (úroveň třídy) a @Valid
(úroveň parametrů).
Vzhledem k tomu, že Spring JPA používá pod sebou Hibernate, podporuje Bean Validation i pro třídy entit. Vezměte prosím na vědomí, že pravděpodobně není dobrý nápad v mnoha případech spoléhat na tuto úroveň ověřování, protože to znamená, že veškerá logika se dříve zabývala neplatnými objekty.
Rozhraní Spring Validation
Spring definuje své vlastní rozhraní pro validaci Validator (org.springframework.validation.Validator). Lze jej nastavit pro konkrétní instanci DataBinder a implementovat ověření bez anotací (nedeklarativní přístup).
K implementaci tohoto přístupu bychom potřebovali:
- Implementujte rozhraní Validator
- Přidat validátor
Implementujte rozhraní Validator
Implementujte rozhraní Validator, například umožňuje pracovat s naším SimpleDto
třída:
@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");
}
}
}
}
Zde zkontrolujte, zda je vytvořen Datetime
časové razítko je v budoucnosti.
Přidat validátor
Přidejte implementaci Validator do DataBinder
:
@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;
}
}
Nyní máme SimpleDto
ověřeno pomocí omezujících anotací a naší vlastní implementace Spring Validation.