一:前言
我们在写接口时,会对接口参数进行一些验证,比如非空必填、字段长度等等,代码就会有大量的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框架的理解同样可以更进一步。
哈哈~~ 又学会了一个新技能,加油吧 💪