SpringBoot自定义校验注解
校验注解的作用
系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的。所以我们可能会写大量的 if else 等判断逻辑,特别是在不同方法出现相同的数据时,校验的逻辑代码会反复出现,导致代码冗余,阅读性和可维护性极差。 自定义校验注解引入依赖
Hibernate框架中有一个组件 hibernate-validator 专门用于数据校验,在平常的Spring项目中虽然数据层不使用Hibernate做 ORM 框架,但是 hibernate-validator 也经常被集成来做数据校验。 org.hibernate.validator hibernate-validator 6.1.7.Final
下面我们写一个用于 URL 校验的注解,实现一个简单的网站信息管理的 URL 校验,做校验的方式我们也使用现成的apache工具包中提供的校验工具。 commons-validator commons-validator 1.7 实现注解
校验注解 /** * 会将注解信息包含在javadoc中 */ @Documented /** * 1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃; * 2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃, * 这是默认的生命周期; * 3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在; * * 一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解 * 如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解; * 如果只是做一些检查性的操作,比如 @Override 和 @Deprecated,使用SOURCE 注解。 */ @Retention(RetentionPolicy.RUNTIME) /** * 作用在字段上 * TYPE - 作用在类上面 * FILED - 作用在属性上面 * METHOD - 作用在方法上 * CONSTRUCTION - 作用在构造器上 * PARAM - 作用在方法参数上 * 允许多种的情况是 @Target({ElementType.FIELD,ElementType.METHOD}) */ @Target(ElementType.FIELD) /** * 对应校验类 */ @Constraint(validatedBy = IsUrlValidator.class) public @interface IsUrl { /** * 是否 强校验 * @return */ boolean required() default true; /** * 校验不通过返回信息 * @return */ String message() default "请输入正确的url"; /** * 所属分组,即在有分组的情况下,只校验所在分组参数 * @return */ Class<?>[] groups() default {}; /** * 主要是针对bean,很少使用 * * @return 负载 */ Class<? extends Payload>[] payload() default {}; }
校验类
校验类需要实现 ConstraintValidator 接口,第一个泛型为注解,第二个为校验的数据类型。
实现这个接口必须要重写 isValid() 方法,在其中实现主要的校验逻辑。 public class IsUrlValidator implements ConstraintValidator { private boolean isRequired; /** * 初始化,获取是否强校验 * @param constraintAnnotation */ @Override public void initialize(IsUrl constraintAnnotation) { isRequired = constraintAnnotation.required(); } @Override public boolean isValid(String s, ConstraintValidatorContext context) { if (!isRequired){ return true; }else { UrlValidator validator = UrlValidator.getInstance(); return validator.isValid(s); } } }使用自定义注解
创建 Insert 、 Update 分组别用于区分和开启校验
用于分组的类需要继承 javax.validation.groups.Default 接口 public interface Update extends Default {} public interface Insert extends Default {}
创建一个 WebSite 类,对其中 url 、 alternateUrl 进行校验,这个字段分别属于 Insert 分组、 Update 分组的时候进行字段校验。 public class WebSite { /** * id */ private Integer id; /** * 网站名称 */ private String name; /** * 网址 */ @IsUrl(groups = Insert.class) private String url; /** * 备用网址 */ @IsUrl(groups = Update.class) private String alternateUrl; }
具体校验方式如下,在insert接口对 Insert 分组进行校验,也就是校验 url 属性,在updateAlternate接口对 Update 分组进行校验,也就是对 alternateUrl 字段进行校验。 @RestController @RequestMapping("/website") public class WebSiteController { @RequestMapping("/insert") public void insert(@RequestBody @Validated(Insert.class) WebSite site){ System.out.println(site); } @RequestMapping("/updateAlternate") public void updateAlternateUrl(@RequestBody @Validated(Update.class) WebSite site){ System.out.println(site); } }
若校验不通过,代码会抛出 MethodArgumentNotValidException 异常,我们实现一个统一异常处理类来处理这个异常报错,并返回校验提示信息。 @ControllerAdvice @Slf4j public class GlobalExceptionHandler { // 处理接口参数数据格式错误异常 @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseBody public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) { List allErrors = e.getBindingResult().getAllErrors(); String message = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(";")); log.error("{}请求,发生参数校验异常:{}",request.getServletPath(),message); return message; } }
使用http工具调用接口,返回相关信息
首先使用一个错误的 url 参数调用 insert 接口,校验不通过,但是调用 updateAlternate 接口可以通过。 POST http://localhost:8080/website/insert Content-Type: application/json { "id": 1, "name": "百度", "url":"htps://www.baidu.com/", "alternateUrl":"https://www.baidu.com/" } ### POST http://localhost:8080/website/updateAlternate Content-Type: application/json { "id": 1, "name": "百度", "url":"htps://www.baidu.com/", "alternateUrl":"https://www.baidu.com/" }
调用 insert 接口的返回及日志打印如下 HTTP/1.1 200 Content-Type: text/plain; charset=UTF-8 Content-Length: 21 Date: Wed, 02 Mar 2022 15:30:23 GMTKeep-Alive: timeout=60 Connection: keep-alive 请输入正确的url -------------------------------------- xxx.GlobalExceptionHandler : /website/insert请求,发生参数校验异常:请输入正确的url常用校验注解
注解
释义
@Null
被注释的元素必须为 null
@NotNull
被注释的元素必须不为 null
@AssertTrue
被注释的元素必须为 true
@AssertFalse
被注释的元素必须为 false
@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)
被注释的元素的大小必须在指定的范围内,元素必须为集合,代表集合个数
@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past
被注释的元素必须是一个过去的日期
@Future
被注释的元素必须是一个将来的日期
@Length(min=, max=)
被注释的字符串的大小必须在指定的范围内,必须为数组或者字符串,若微数组则表示为数组长度,字符串则表示为字符串长度
@NotEmpty
被注释的字符串的必须非空
@Range(min=, max=)
被注释的元素必须在合适的范围内
@NotBlank
被注释的字符串的必须非空
@Pattern(regexp = )
正则表达式校验
@Valid
对象级联校验,即校验对象中对象的属性