Java 校验注解
攻城狮Chova 人气:0@Valid和@Validated
@Valid和@Validated比较
- 相同点:
- @Valid注解和 @Validated注解都是开启校验功能的注解
- 不同点:
- @Validated注解是Spring基于 @Valid注解的进一步封装,并提供比如分组,分组顺序的高级功能
- 使用位置不同:
- @Valid注解 : 可以使用在方法,构造函数,方法参数和成员属性上
- @Validated注解 : 可以用在类型,方法和方法参数上. 但是不能用在成员属性上
@Valid高级使用
@Valid级联校验
- 级联校验: 也叫嵌套检测.嵌套就是一个实体类包含另一个实体类
- @Valid和可以用在成员属性的字段上,因此 @Valid可以提供级联校验
- 示例:
@Data public class Hair { @NotBlank(message = "头发长度必须提交!") private Double length; @NotBlank(message = "头发颜色必须提交!") private String color; } @Data public class Person { @NotBlank(message = "用户姓名必须提交!") @Size(min=2, max=8) private String userName; // 添加@Valid注解实现嵌套检测 @Valid @NotEmpty(message = "用户要有头发!") private List<Hair> hairs; } @PostMapping("/person") public Result addPerson(@Valid @RequestBody Person person) { return Result.buildSuccess(person); }
- 只是在方法参数前面添加 @Valid和 @Validated注解,不会对嵌套的实体类进行校验.要想实现对嵌套的实体类进行校验,需要在嵌套的实体类属性上添加 @Valid注解
@Validated高级使用
@Validated分组校验
- 分组校验:
- 对指定的组开启校验,可以分别作用于不同的业务场景中
- 分组校验是由 @Validated注解中的value提供的
- groups:
- JSR 303校验注解中的分组方法groups
- 示例:
@Data public class PersonGroup { public interface AddGroup {} public interface UpdateGroup {} // @Validated注解value方法指定分组UpdateGroup.class时校验 @NotBlank(message = "用户ID必须提交!", groups = UpdateGroup.class) private String id; // @Validated注解value方法指定分组AddGroup.class或者分组UpdateGroup.class时校验 @NotBlank(message = "用户的姓名必须提交!", groups = {AddGroup.class, UpdateGroup.class}) private String name; // @Validated注解value方法未指定分组时校验 @Range(min = 1, max = 200, message = "用户的年龄必须提交!") private int age; }
- 开启分组校验: 通过 @Validated注解的value方法对指定的分组开启校验
@RestController @RequestMapping("/person") public class PersonGroupController { // 不指定分组时校验 @GetMapping("/person") public Result getPerson(@Validated @RequestBody PersonGroup person) { return Result.buildSuccess(person); } // 指定AddGroup分组校验 @PostMapping("/person") public Result addPerson(@Validated(value = PersonGroup.AddGroup.class) @RequestBody PersonGroup person) { return Result.buildSuccess(person); } // 指定UpdateGroup分组校验 @PutMapping("/person") public Result updatePerson(@Validated(value = PersonGroup.updateGroup.class) @RequestBody PersonGroup person) { return Result.buildSuccess(person); } }
- 校验方法添加groups的值来指定分组,只有使用 @Validated注解的value的值指定这个分组时,开会开启注解的校验数据的功能
@Validated分组校验顺序
- 默认情况下,分组间的约束是无序的,但是在一些特殊的情况下可能对分组间的校验有一定的顺序
- 比如第二组的分组的约束的校验需要依赖第一组的稳定状态来进行,此时,要求分组间的约束校验一定要有顺序
- 分组校验顺序通过使用 @GroupSequence注解实现
- 示例:
@Data public class UserGroupSequence { public interface FirstGroup {} public interface SecondGroup {} // 使用GroupSequence定义分组校验顺序:按照FirstGroup,SecondGroup分组顺序进行校验 @GroupSequence({FirstGroup.class, SecondGroup.class}) public interface Group {} @NotEmpty(message = "用户ID必须提交!", group = FirstGroup.class) private String userId; @NotEmpty(message = "用户姓名必须提交!", group = FirstGroup.class) @Size(min = 2, max = 8, message = "用户姓名的长度在2~8之间", goup = Second.class) private String userName; }
@RestController @RequestMapping("/user") public class UserGroupSequenceController { // 这里方法中@Validated注解value的值是Group.class @PostMapping("/user") public Result addGroup(@Validated(value = Group.class) @RequestBody UserGroupSequence user) { return Result.buildSuccess(user); } }
- 使用 @GroupSequence注解指定分组校验顺序后,第一组分组的约束的校验没有通过后,就不会进行第二组分组的约束的校验
@Validated非实体类校验
- 在非实体类上添加 @Validated注解对非实体类进行校验
@Validated public class AnnotationController { @GetMapping("/person") public Result getAge(@Range(min = 2, max = 8, message = "年龄在3~8岁!") @RequestParam int age) { return Result.buildSuccess(age); } }
- 在GlobalExceptionHandler中添加全局统一异常处理方法:
@ExceptionHandler(ConstraintViolationException.class) @ResponseBody public Result resolveConstraintViolationException(ConstraintVilationException exception) { Set<ConstraintVilation<?>> constraintVilations = exception.getConstraintVilations(); // 处理异常信息 if (!CollectionUtils.isEmpty(constraintVilations)) { StringBuilder messageBuilder = new StringBuilder(); for (ConstraintVilation constraintViolation : constraintVilations) { messageBuilder.append(constraintVilation.getMessage()).append(","); } String errorMessage = messageBuilder.toString(); if (errorMessage.length() > 1) { errorMessage.substring(0, errorMessage.length() - 1); } return Result.builderFailure(ErrorStatus.ILLEGAL_DATA.getCode(), errorMessage); } return Result.builderFailure(ErrorStatus.ILLEGAL_DATA.getCode(), exception.getMessage()) }
@PathVariable
- @PathVariable的作用: 用来指定请求URL路径里面的变量
- @PathVariable和 @RequestParam的区别:
- @PathVariable用来指定请求URL中的变量
- @RequestParam用来获取静态的URL请求入参
正则表达式校验
- 使用正则表达式校验 @PathVariable指定的路径变量
// 请求路径中的id必须是数字,否则寻找不到这个路径404 @GetMapping("/user/{id:\\d+}") public Result getId(@PathVariable(name="id") String userId) { return Result.buildSuccess(userId); }
继承BasicErrorController类
- @ControllerAdvice注解只能处理进入控制器方法抛出的异常
- BasicErrorController接口可以处理全局异常
- @PathVariable路径校验异常不是控制器方法抛出的,此时还没有进入控制器方法:
- BasicErrorController处理异常,比如404异常时,会跳转到 /error路径,此时会返回错误的html页面
- 为了保证返回结果统一,继承BasicErrorController类,重写BasicErrorController接口中的错误处理方法
@RestController public class PathErrorController extends BasicErrorController { @Autowired public PathErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) { super(errorAttributes, serverProperties.getError(), errorViewResolvers); } /** * 处理html请求 */ @Override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)); ModelAndView modelAndView = new ModelAndView("pathErrorPage", model, status); return modelAndView; } /** * 处理json请求 */ @Override public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); Map<String, Object> responseBody = new HashMap<>(8); responseBody.put("success", false); responseBody.put("code", body.get("status")); responseBody.put("message", body.get("error")); return new ResponseEntity<>(responseBody, HttpStatus.OK); } }
自定义校验注解
- 使用场景:
- 对某一个只能输入指定值的字段进行校验. 此时需要使用自定义注解实现
- 定义自定义的注解 @Show :
@Documented @Constraint(validateBy = {Show.ShowConstraintValidator.class}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Rentation(RUNTIME) public @interface Show { String message() default "{com.oxford.annotation.Show.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int[] value(); class ShowConstraintValidator implements ConstraintValidator<Show, Integer> { private Set<Integer> set = new HashSet<>(); /** * 初始化操作 * 获取value属性指定的数字,保存到Set集合中 */ @Override public void initilize(Show constraintAnnotation) { int[] value = constraintAnnotation.value(); for (int v : value) { set.add(i); } } @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } } }
- 注意点:
- @Constraint注解:
- 将自定义的注解和实现的校验类联系起来
- 自定义校验注解类需要实现ConstraintValidator<A extends Annotation, T> 接口
- 接口中第一个泛型参数表示的是自定义注解类
- 接口中第二个泛型参数表示的是校验的属性的值的类型
- initialize() 方法:
- 获取到自定义注解中的相关的数据
- isValid() 方法:
- 实现自定义的校验逻辑
- 返回boolean类型的校验结果
- @Constraint注解:
- 自定义注解的使用:
@Data public class AnnotationQuery { @Show(value = {0, 1}, message = "数值只能是0或者1") private Integer isShow; }
@PostMapping("/annotation") public Result addAnnotation(@Validated @RequestBody AnnotationQuery annotation) { return Result.buildSuccess(annotation); }
加载全部内容