Bien que les annotations standard Spring (@NotBlank, @NotNull, @Min, @Size, etc.) couvrent de nombreux cas d'utilisation lors de la validation des entrées utilisateur, il arrive parfois que nous devions créer une logique de validation personnalisée pour un type d'entrée plus spécifique. . Dans cet article, je vais montrer comment créer des annotations personnalisées pour la validation.
Nous devons ajouter la dépendance spring-boot-starter-validation à notre fichier pom.xml.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Créons des annotations personnalisées pour valider les attributs du fichier, tels que l'extension du fichier, la taille du fichier et le type MIME.
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {FileExtensionValidator.class} ) public @interface ValidFileExtension { String[] extensions() default {}; String message() default "{constraints.ValidFileExtension.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {FileMaxSizeValidator.class} ) public @interface ValidFileMaxSize { long maxSize() default Long.MAX_VALUE; // MB String message() default "{constraints.ValidFileMaxSize.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {FileMimeTypeValidator.class} ) public @interface ValidFileMimeType { String[] mimeTypes() default {}; String message() default "{constraints.ValidFileMimeType.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Décomposons les composants de ces annotations :
public class FileExtensionValidator implements ConstraintValidator<ValidFileExtension, MultipartFile> { private List<String> extensions; @Override public void initialize(ValidFileExtension constraintAnnotation) { extensions = List.of(constraintAnnotation.extensions()); } @Override public boolean isValid(MultipartFile file, ConstraintValidatorContext constraintValidatorContext) { if (file == null || file.isEmpty()) { return true; } var extension = FilenameUtils.getExtension(file.getOriginalFilename()); return StringUtils.isNotBlank(extension) && extensions.contains(extension.toLowerCase()); } }
public class FileMaxSizeValidator implements ConstraintValidator<ValidFileMaxSize, MultipartFile> { private long maxSizeInBytes; @Override public void initialize(ValidFileMaxSize constraintAnnotation) { maxSizeInBytes = constraintAnnotation.maxSize() * 1024 * 1024; } @Override public boolean isValid(MultipartFile file, ConstraintValidatorContext constraintValidatorContext) { return file == null || file.isEmpty() || file.getSize() <= maxSizeInBytes; } }
@RequiredArgsConstructor public class FileMimeTypeValidator implements ConstraintValidator<ValidFileMimeType, MultipartFile> { private final Tika tika; private List<String> mimeTypes; @Override public void initialize(ValidFileMimeType constraintAnnotation) { mimeTypes = List.of(constraintAnnotation.mimeTypes()); } @SneakyThrows @Override public boolean isValid(MultipartFile file, ConstraintValidatorContext constraintValidatorContext) { if (file == null || file.isEmpty()) { return true; } var detect = tika.detect(TikaInputStream.get(file.getInputStream())); return mimeTypes.contains(detect); } }
Ces classes sont des implémentations de l'interface ConstraintValidator et contiennent la logique de validation réelle.
Pour FileMimeTypeValidator, nous utiliserons Apache Tika (une boîte à outils conçue pour extraire les métadonnées et le contenu de nombreux types de documents).
Créons une classe TestUploadRequest destinée à gérer les téléchargements de fichiers, spécifiquement pour un fichier PDF.
@Data public class TestUploadRequest { @NotNull @ValidFileMaxSize(maxSize = 10) @ValidFileExtension(extensions = {"pdf"}) @ValidFileMimeType(mimeTypes = {"application/pdf"}) private MultipartFile pdfFile; }
@RestController @Validated @RequestMapping("/test") public class TestController { @PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public ResponseEntity<String> testUpload(@Valid @ModelAttribute TestUploadRequest request) { return ResponseEntity.ok("test upload"); } }
Une annotation de validation personnalisée peut également être définie au niveau de la classe pour valider une combinaison de champs au sein d'une classe.
Créons l'annotation @PasswordMatches pour garantir que deux champs de mot de passe correspondent dans une classe.
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {PasswordMatchesValidator.class} ) public @interface PasswordMatches { String message() default "{constraints.PasswordMatches.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
public interface PasswordDto { String getPassword(); String getConfirmPassword(); }
public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, PasswordDto> { @Override public boolean isValid(PasswordDto password, ConstraintValidatorContext constraintValidatorContext) { return StringUtils.equals(password.getPassword(), password.getConfirmPassword()); } }
L'interface PasswordDto est une interface pour les objets qui contiennent un mot de passe et un champ de confirmation du mot de passe.
La classe PasswordMatchesValidator implémente l'interface ConstraintValidator et contient la logique permettant de valider que les champs de mot de passe et de confirmation du mot de passe correspondent.
Créons une classe RegisterAccountRequest destinée à gérer les données d'enregistrement des utilisateurs.
@PasswordMatches @Data public class RegisterAccountRequest implements PasswordDto { @NotBlank private String username; @NotBlank @Email private String email; @NotBlank @ToString.Exclude private String password; @NotBlank @ToString.Exclude private String confirmPassword; }
@RestController @Validated @RequestMapping("/auth") public class AuthController { @PostMapping("/register") public ResponseEntity<String> register(@RequestBody @Valid RegisterAccountRequest request) { return ResponseEntity.ok("register success"); } }
Dans ce court article, nous avons découvert à quel point il est simple de créer des annotations personnalisées pour vérifier un champ ou une classe. Le code de cet article est disponible sur mon Github.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!