SpringBoot-(二十二)Validator的参数校验与自定义注解实现参数、类校验
本文最后更新于:September 4, 2022 am
SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
目录
实现步骤
- 一:写自定义注解。
- 二:自定义实现校验规则。
- 三:测试。
导入依赖
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
|
或者:
| <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency>
|
自定义校验注解
以下内容均为常规配置,即模版。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.tothefor.annotation;
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = {CheckPhoneValidator.class}) @Target(value = {ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckPhone {
String message() default "手机号格式不正确。";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { CheckPhone[] value(); } }
|
编写校验实现类
这里的校验只是简单的进行了校验,可根据自定义进行校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.tothefor.annotation;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Pattern;
public class CheckPhoneValidator implements ConstraintValidator<CheckPhone, String> {
private static final Pattern PHONE_CHECK = Pattern.compile("(?:0|86|\\+86)?1[3456789]\\d{9}");
@Override public void initialize(CheckPhone constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); }
@Override public boolean isValid(String phone, ConstraintValidatorContext context) { if (phone == null || phone.length() < 11) { return false; } return PHONE_CHECK.matcher(phone).matches(); } }
|
测试校验
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.tothefor.pojo.dto;
import com.tothefor.annotation.CheckPhone; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString;
@Data @AllArgsConstructor @NoArgsConstructor @ToString public class Person { private String name; private int age; @CheckPhone private String phone; }
|
URL测试
| @RequestMapping("/check") public Object checkO(@RequestBody @Validated Person person){ return new JSONObject() .fluentPut("check",person) ; }
|
测试类
| @Test void testCheckPhone(){ ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); Validator validator = validatorFactory.getValidator(); Person p = new Person(); p.setPhone("324"); Set<ConstraintViolation<Person>> validate = validator.validate(p); System.out.println(validate); }
|
自定义类校验
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.tothefor.annotation;
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = {CheckDateValidator.class}) @Target(value = {ElementType.METHOD, ElementType.FIELD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckDate {
String message() default "不在活动时间内!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { CheckDate[] value(); } }
|
校验实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.tothefor.annotation;
import com.tothefor.pojo.dto.CheckDateDTO;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext;
public class CheckDateValidator implements ConstraintValidator<CheckDate, CheckDateDTO> {
@Override public void initialize(CheckDate constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); }
@Override public boolean isValid(CheckDateDTO date, ConstraintValidatorContext context) { if (date == null) { return false; } if (date.getNow().after(date.getStart()) && date.getNow().before(date.getEnd())) { return true; } return false; } }
|
被校验类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.tothefor.pojo.dto;
import com.tothefor.annotation.CheckDate; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString;
import java.util.Date;
@Data @AllArgsConstructor @NoArgsConstructor @ToString @CheckDate public class CheckDateDTO { private Date start; private Date end; private Date now; }
|
测试
| @Test void testCheckDate(){ ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); Validator validator = validatorFactory.getValidator(); CheckDateDTO date = new CheckDateDTO(); date.setStart(DateUtilsLee.getTime("2022-08-20 22:12:12")); date.setEnd(DateUtilsLee.getTime("2022-08-22 22:22:22")); date.setNow(new Date()); Set<ConstraintViolation<CheckDateDTO>> validate = validator.validate(date); System.out.println(validate);
}
|
加入全局异常处理器
异常捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.tothefor.DBTest;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class) public MethodArgumentNotValidException handler(MethodArgumentNotValidException e, HttpServletRequest request){ return e; }
}
|
异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.tothefor.DBTest;
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice(basePackages = "com.tothefor.DBTest") public class ResponseBody implements ResponseBodyAdvice<Object> {
@Autowired private ObjectMapper objectMapper;
@Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; }
@SneakyThrows @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if(o==null){ return R.SUCCESS(); } if(o instanceof BizErrorException){ BizErrorException eO = (BizErrorException) o; return R.FAIL(eO.getCode(),eO.getException()); } if(o instanceof MethodArgumentNotValidException){ MethodArgumentNotValidException e = (MethodArgumentNotValidException)o; return R.FAIL(BizErrors.ERRORS_Validator.code,BizErrors.ERRORS_Validator.exception); } if(o instanceof String){ return objectMapper.writeValueAsString(R.SUCCESS(o)); } return R.SUCCESS(o); } }
|
异常码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.tothefor.DBTest;
import lombok.AllArgsConstructor;
@AllArgsConstructor public enum BizErrors implements BizError { ERROR_SYSTEM(1000000,"系统异常"), ERRORS_Validator(100001,"参数异常"), ;
int code;
String exception;
@Override public int code() { return code; }
@Override public String exception() { return exception; } }
|
测试
| @RequestMapping("/check") public Object checkO(@RequestBody @Validated Person person){ return new JSONObject() .fluentPut("check",person) ; }
|
异常页面显示
| { "success": false, "code": 100001, "message": "参数异常", "timestamp": 1662254126571, "data": null }
|
自定义提示信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.tothefor.DBTest;
import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.List;
@ControllerAdvice(basePackages = "com.tothefor.DBTest") public class ResponseBody implements ResponseBodyAdvice<Object> {
@Autowired private ObjectMapper objectMapper;
@Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; }
@SneakyThrows @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if(o==null){ return R.SUCCESS(); } if(o instanceof BizErrorException){ BizErrorException eO = (BizErrorException) o; return R.FAIL(eO.getCode(),eO.getException()); } if(o instanceof MethodArgumentNotValidException){ MethodArgumentNotValidException e = (MethodArgumentNotValidException)o; List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); StringBuilder sb = new StringBuilder(); for (FieldError it : fieldErrors){ sb.append(it.getField()+" "); sb.append(it.getCode()+" "); sb.append(it.getDefaultMessage()+" "); } return R.FAIL(BizErrors.ERRORS_Validator.code,sb.toString()); } if(o instanceof String){ return objectMapper.writeValueAsString(R.SUCCESS(o)); } return R.SUCCESS(o); } }
|
其中,处理提示信息为:(这部分可自行提取为一个方法进行处理)
| List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors(); StringBuilder sb = new StringBuilder(); for (FieldError it : fieldErrors){ sb.append(it.getField()+" "); sb.append(it.getCode()+" "); sb.append(it.getDefaultMessage()+" "); }
|
- getField():获取校验失败的参数名称。
- getCode():获取校验的注解名称。
- getDefaultMessage():获取注解的提示信息。@CheckPhone 获取的是注解默认的提示信息;@CheckPhone(“asdf”) 获取的提示信息为:”asdf”。