Validation du haricot de printemps
La validation des données n'est pas un sujet nouveau dans le développement d'applications Web.
Nous examinons brièvement la validation des données dans l'écosystème Java en général et dans Spring Framework en particulier. La plate-forme Java est devenue la norme de facto pourla mise en œuvre de la validation des données c'est la spécification de validation de bean. La spécification Bean Validation a plusieurs versions :
- 1.0 (JSR-303),
- 1.1 (JSR-349),
- 2.0 (JSR 380) – la dernière version
Cette spécification définit un ensemble de composants, d'interfaces et d'annotations. Cela fournit un moyen standard de mettre des contraintes sur les paramètres et de renvoyer les valeurs des méthodes et des paramètres des constructeurs, de fournir une API pour valider les objets et les graphiques d'objets.
Un modèle déclaratif est utilisé pour mettre des contraintes sous forme d'annotations sur les objets et leurs champs. Il existe des annotations prédéfinies comme @NotNull
, @Digits
, @Pattern
, @Email
, @CreditCard
. Il est possible de créer de nouvelles contraintes personnalisées.
La validation peut s'exécuter manuellement ou plus naturellement, lorsque d'autres spécifications et cadres valident les données au bon moment, par exemple, une entrée utilisateur, une insertion ou une mise à jour dans JPA.
Validation dans l'exemple Java
Voyons comment cela peut être fait en pratique dans cet exemple simple de validation de bean dans une application Java standard.
Nous avons un objet que nous voulons valider avec tous les champs annotés avec des contraintes.
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
Nous pouvons maintenant l'utiliser dans une simple application Java et valider manuellement l'objet.
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() + "]"
);
}
}
}
Et le résultat dans la console sera :
“[id] [Id can't be less than 1 or bigger than 999999]”
Contrainte de validation et annotation
Créez une contrainte de validation personnalisée et une annotation.
@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;
}
}
Dans l'exemple ci-dessus, les catégories disponibles proviennent d'une simple carte de hachage. Dans des cas d'utilisation réels d'applications, ils peuvent être récupérés à partir de la base de données ou de tout autre service.
Veuillez noter que les contraintes et les validations peuvent être spécifiées et effectuées non seulement au niveau du champ, mais également sur un objet entier.
Lorsque nous devons valider diverses dépendances de champs, par exemple, la date de début, cela ne peut pas être après la date de fin.
Les implémentations les plus largement utilisées de Bean Validation les spécifications sont Hibernate Validator et Apache BVal .
Validation avec Spring
Le framework Spring fournit plusieurs fonctionnalités de validation.
- La prise en charge des versions 1.0, 1.1 (JSR-303, JSR-349) de l'API Bean Validation a été introduite dans Spring Framework à partir de la version 3.
- Spring a sa propre interface Validator qui est très basique et peut être définie dans une instance DataBinder spécifique. Cela pourrait être utile pour implémenter une logique de validation sans annotations.
Validation du bean avec Spring
Spring Boot fournit une validation démarrée qui peut être incluse dans le projet :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Ce starter fournit une version de Hibernate Validator compatible avec le Spring Boot actuel.
En utilisant Bean Validation, nous pourrions valider un corps de requête, des paramètres de requête, des variables dans le chemin (par exemple, / /simpledto/{id} ), ou n'importe quel paramètre de méthode ou de constructeur.
Requêtes POST ou PUT
Dans les requêtes POST ou PUT, par exemple, nous transmettons la charge utile JSON, Spring la convertit automatiquement en objet Java et nous voulons maintenant valider l'objet résultant. Utilisons SimpleDto
objet du 1 exemple :
@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;
}
}
Nous venons d'ajouter @Valid
annotation au SimpleDto
paramètre annoté avec @RequestBody
. Cela indiquera à Spring de traiter la validation avant de faire un appel de méthode réel. En cas d'échec de la validation, Spring lancera une MethodArgument NotValidException
qui, par défaut, renverra une réponse 400 (Bad Request).
Validation des variables de chemin
La validation des variables de chemin fonctionne un peu différemment. Le problème est que nous devons maintenant ajouter des annotations de contrainte directement aux paramètres de méthode plutôt qu'à l'intérieur des objets.
Pour que cela fonctionne, il y a 2 options possibles :
Option 1 :@Annotation validée
Ajouter @Validated
annotation au contrôleur au niveau de la classe pour évaluer les annotations de contrainte sur les paramètres de méthode.
Option 2 :Variable de chemin
Utilisez un objet qui représente la variable de chemin comme dans l'exemple ci-dessous :
@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;
}
}
Dans ce cas, nous avons SimpleDtoIdParam
classe qui contient le simpleDtoId
champ qui sera validé par rapport à une annotation de contrainte Bean standard ou personnalisée. Chemin Nom de la variable (/{simpleDtoId} ) doit être le même que le nom du champ (pour que Spring puisse trouver le setter pour ce champ).
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;
}
}
Contrairement à Request Body validation, variable de chemin la validation lève ConstraintViolationException
au lieu de MethodArgumentNotValidException
. Par conséquent, nous devrons créer un gestionnaire d'exceptions personnalisé.
Le framework Java Spring permet également de valider les paramètres au niveau du service avec @Validated
annotation (niveau classe) et @Valid
(niveau paramètre).
Étant donné que Spring JPA utilise Hibernate en dessous, il prend également en charge la validation de bean pour les classes d'entités. Veuillez noter que ce n'est probablement pas une bonne idée dans de nombreux cas de s'appuyer sur ce niveau de validation, car cela signifie que toute la logique auparavant traitait d'objets non valides.
Interface de validation du printemps
Spring définit sa propre interface pour le validateur de validation (org.springframework.validation.Validator). Il peut être défini pour une instance DataBinder spécifique et implémenter la validation sans annotations (approche non déclarative).
Pour mettre en œuvre cette approche, nous aurions besoin :
- Mettre en œuvre l'interface de validation
- Ajouter un validateur
Mettre en œuvre l'interface du validateur
Implémenter l'interface Validator, par exemple, permet de travailler avec notre SimpleDto
classe :
@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");
}
}
}
}
Vérifiez ici si le Datetime
créé l'horodatage est dans le futur.
Ajouter un validateur
Ajouter l'implémentation du validateur à 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;
}
}
Nous avons maintenant SimpleDto
validé à l'aide d'annotations de contraintes et de notre implémentation personnalisée de Spring Validation.