一:前言

我们在写接口时,会对接口参数进行一些验证,比如非空必填、字段长度等等,代码就会有大量的if - else,重复的代码毫无意义。为了提升方便性和代码的简洁性,JAVA提供了@validated和@valid注解验证,但这只能在controller层生效。接下来我会写常用注解和自定义注解。


二:JAVA常用验证注解

注解

说明

@Null

限制只能为null

@NotNull

限制必须不为null

@AssertFalse

限制必须为false

@AssertTrue

限制必须为true

@DecimalMax(value)

限制必须为一个不大于指定值的数字

@DecimalMin(value)

限制必须为一个不小于指定值的数字

@Digits(integer,fraction)

限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction

@Future

限制必须是一个将来的日期

@Max(value)

限制必须为一个不大于指定值的数字

@Min(value)

限制必须为一个不小于指定值的数字

@Past

限制必须是一个过去的日期

@Pattern(value)

限制必须符合指定的正则表达式

@Size(max,min)

限制字符长度必须在min到max之间

@Past

验证注解的元素值(日期类型)比当前时间早

@NotEmpty

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

@NotBlank

验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格

@Email

验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

参数实体类加注解:

public class OrganizationStructureDetailRequest{
  
  @NotNull(message = "请输入项目ID")
  @ApiModelProperty(notes = "项目ID")
  private Integer projectId;
  
  @NotEmpty(message = "请输入类型code")
  @ApiModelProperty(notes = "类型code")
  private String code;
}

public class User{
  @NotNull(message = "id不能为空")
  @Min(value = 1, message = "id必须为正整数")
  private Integer id;

  @Valid // 嵌套验证必须用@Valid
  @NotNull(message = "props不能为空")
  @Size(min = 1, message = "props至少要有一个自定义属性")
  private List<Prop> props;
}

Controller层加上@Valid注解验证

@ResponseBody
@ApiOperation(value = "组织结构详情")
@PostMapping("detail")
public List<OrganizationStructureDetail> detail(@Valid OrganizationStructureDetailRequest request) {
   return organizationStructureService.detail(request);
} 
  
@RequestMapping("/user/add")
public void addUser(@Validated User) {
     doSomething();
 }

@Validated 和 @Valid 总结

@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid: 用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

三:自定义验证注解

//@Target指明这个注解要作用在什么地方,可以是对象、域、构造器等,因为要作用在IdCard域上,因此这里选FIELD
//@Retention指明了注解的生命周期,可以有SOURCE(仅保存在源码中,会被编译器丢弃),CLASS(在class文件中可用,会被VM丢弃)以及RUNTIME(在运行期也被保留),这里选择了生命周期最长的RUNTIME
//@Documented注解指明这个注释是由 javadoc记录的,在默认情况下也有类似的记录工具。如果一个类型声明被注释了文档化,它的注释成为公共API的一部分。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mobile {
  String message() default "请输入正确的手机号码";

  int subCode() default 31900;
}

写自定义验证注解参数类,可以在除了Controller层之外的地方调用验证参数。以下方法是验证一个类中有单个字段、集合、实体类对象。

public class ParamCheckUtil {
 /**
   * 表单参数验证
   * @param object 参数对象
   * @throws ApiException
   */
  public static void formParamsValidate(Object object) throws ApiException {
    if(null == object){
      return;
    }
    Class<?> clazz = object.getClass();
    //获得类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      //设置Field对象的Accessible的访问标志位为Ture,就可以通过反射获取私有变量的值,在访问时会忽略访问修饰符的检查
      field.setAccessible(true);
      Object value = null;
      try {
        value = field.get(object);
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
      Class<?> typeClass = field.getType();
      //判断这个字段类型是否是集合,如果是需要循环验证集合中的对象字段。
      if (List.class.isAssignableFrom(typeClass)){
        if(null != value){
          List list = (List)value;
          if (list.size() > 0) {
            Iterator var15 = list.iterator();
            while(var15.hasNext()) {
              Object o = var15.next();
              formParamsValidate(o);
            }
          }
        }
      }else if (BaseApiRequest.class.isAssignableFrom(typeClass)) {
        //判断这个字段类型是否是实体类,如果是需要验证这个对象中的字段。
        formParamsValidate(value);
      }else{
        fieldValueValidate(field, value);
      }
    }
  }
  
  /**
   * 验证字段对应的注解
   * @param field
   * @param value
   * @throws ApiException
   */
  private static void fieldValueValidate(Field field, Object value) throws ApiException {
    //判断Mobile注解是否在field字段上
  	if (field.isAnnotationPresent(Mobile.class)) {
      Mobile idCard = field.getAnnotation(Mobile.class);
      if (!MyValidator.isMobile("" + value)) {
        throw new ApiException(idCard.message());
      }
    }
	if (field.isAnnotationPresent(Length.class)) {
	  Length length = field.getAnnotation(Length.class);
	  if (!MyValidator.isLength(length.min(), length.max(), "" + value)) {
	    throw new ApiException(length.message());
	  }
	}
}
public abstract class MyValidator {
  private MyValidator() {
  }
  
  public static boolean isMobile(String input) {
   if (input.length() != 11) {
     return false;
   } else {
     Pattern p = null;
     Matcher m = null;
     boolean b = false;
     p = Pattern.compile("^[1][3,4,5,6,7,8,9][0-9]{9}$");
     m = p.matcher(input);
     b = m.matches();
     return b;
   }
 }
 
 public static boolean isLength(int min, int max, String input) {
    int length = input.length();
    return length >= min && length <= max;
  }
}

总结:
以上是我自己写的一套自定义验证注解参数,因为需求设计原因导致不能在Controller层调用注解参数,只能自己写一套方法,在Service层根据业务逻辑调用ParamCheckUtil.formParamsValidate()方法验证对象数据。使代码更简洁。通过对自定义注解的使用可以很好加深对动态代理这些概念的认识,对spring框架的理解同样可以更进一步。
哈哈~~ 又学会了一个新技能,加油吧 💪

JAVA数据验证list java参数验证_JAVA数据验证list