前言
后台服务处理前端的请求时,会有这样的一种需求,即校验请求中的参数是否符合校验规则。校验参数是否符合的一种方法是,罗列请求参数,基于校验规则一个一个的校验参数,如果存在不符合的,就返回字段值不符合规则的提示,通过就向下执行。这种方法是可以的,但是不通用,因为需要校验请求参数的地方太多了,罗列式的校验参数会显得效率低下,且,字段的校验规则都是大同小异的,这就有重复造轮子的可能,这是不可取的。所以,应该提取字段的通用校验代码,做到只要在接收参数的model中,通过注解在需要校验的字段上定义好字段不通过时的错误提示,再定义好字段的校验器(校验器是可复用的),即可校验model中的参数是否符合校验规则,如果不通过,返回字段对应的错误提示,如果通过,就返回null。接下来,就分别从代码包结构,两个注解,校验器、校验主逻辑和示例等几个方面,介绍对model的字段进行通用校验的代码。
包结构
如下为model字段校验通用代码的包结构,其中,annotation包中的两个注解之一JavaField是标注在字段上的,用于给字段添加校验规则,JavaFileds用于实现可重复添加相同的注解。validator包中的接口Validator是字段校验器抽象接口,用于定义字段的校验规则,impl包中的是实现Validator接口的实现类,是字段的不同校验器。ValidateUtils是基于注解的实现字段校验的主逻辑代码。
注解
JavaField注解是添加到需要进行字段值校验的字段上的,用于定义字段校验规则,其有两个参数,一是message,为字段校验不通过时的错误提示,二是validator,它的类型是Class,用于存储校验器的Class对象。
JavaField
package com.nursehealth.util.common.ValidateUtils.annotation;
import java.lang.annotation.*;
/**
* @author wengym
* @version 1.0
* @desc Java字段校验注解
* @date 2022/12/20 11:05
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(JavaFields.class)
public @interface JavaField {
/**
* 校验不通过的提示信息
*/
String message();
/**
* 规则校验器
*/
Class validator();
}
由于字段的校验可能是多重的,如先要判断字段值是否为空,如果不为空,还要判断值是否符合其他规则,而默认情况下,字段上是不能添加重复的注解的,所以需要让注解可重复添加。为了做到注解可重复添加,首先定义JavaFields注解(注解的名称可随意),该注解需要定义value()方法,注意,该方法的名称就是value而不能是其他的,方法的返回值是需要重复添加的注解数组,即JavaField[]。然后在需要重复添加的注解中,添加@Repeatable(JavaFields.class),如此,即可让注解重复添加。
JavaFields
package com.nursehealth.util.common.ValidateUtils.annotation;
import java.lang.annotation.*;
/**
* @author wengym
* @version 1.0
* @desc Java字段校验注解
* @date 2022/12/27 11:05
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaFields {
JavaField[] value();
}
校验器
校验器中只有一个校验字段的方法,该方法的作用是接收字段值,然后校验字段值是否符合规则,如果符合则返回true,如果不符合则返回false。校验器是可以复用的,即同一类的校验只要定义一个校验器即可,不用重复写,如判空校验器,只要定义了,就可处处调用。
接口
package com.nursehealth.util.common.ValidateUtils.validator;
public interface Validator {
/**
* 校验字段值是否符合规则,通过为true,不通过为false
*
* @param fieldValue
*
* @author wengym
*
* @date 2022/12/20 11:08
*
* @return java.lang.Boolean
*/
Boolean validateField(Object fieldValue);
}
接口实现类
EmptyValidator:判空校验器
字段值为null,为空串,或为为null字符串,表示字段为空,返回false,表示不通过,否则返回true,表示通过。
package com.nursehealth.util.common.ValidateUtils.validator.impl;
import com.nursehealth.util.common.ValidateUtils.validator.Validator;
/**
* @author wengym
* @version 1.0
* @desc 空字段校验器
* @date 2022/12/20 11:12
*/
public class EmptyValidator implements Validator {
@Override
public Boolean validateField(Object fieldValue) {
if (fieldValue == null || "".equals(fieldValue) || "null".equals(String.valueOf(fieldValue).trim())) {
return false;
}
return true;
}
}
GenderValidator:性别校验器
性别用1表示男,2表示女,性别只能是1和2,性别为1和2时,返回true,表示通过,否则返回false,表示不通过。
package com.nursehealth.util.common.ValidateUtils.validator.impl;
import com.nursehealth.util.common.ValidateUtils.validator.Validator;
/**
* @author wengym
* @version 1.0
* @desc 性别校验器
* @date 2022/12/27 11:05
*/
public class GenderValidator implements Validator {
@Override
public Boolean validateField(Object fieldValue) {
String gender = (String)fieldValue;
if ("1".equals(gender) || "2".equals(gender)) {
return true;
}
return false;
}
}
校验主逻辑
校验主逻辑要做的就是遍历model字段,再取出字段值,取出字段的校验注解(有多个时是JavaFields,单个时是JavaField,需要分开处理),再实例化校验注解的校验器,然后就基于字段值调用校验器的校验字段方法(validateField),返回的结果如果为true,表示校验通过,则向下执行,如果为false,表示校验不通过,则停止向下执行,并返回校验注解的错误提示信息。如果遍历完字段后,还是没有不通过的字段,则返回null,表示model的参数都通过了校验。
package com.nursehealth.util.common.ValidateUtils;
import com.nursehealth.util.common.ValidateUtils.annotation.JavaField;
import com.nursehealth.util.common.ValidateUtils.annotation.JavaFields;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author wengym
* @version 1.0
* @desc Java字段检验工具类
* @date 2022/12/20 11:17
*/
public class ValidateUtils {
/**
* 检验model中的字段值是否符合规则
*
* @param model
* @return java.lang.String
* @author wengym
* @date 2022/12/20 11:18
*/
public static <T> String validate(T model) {
if (model == null) {
throw new NullPointerException("参数不能为null");
}
Class cls = model.getClass();
Field[] fields = cls.getDeclaredFields();
String validateResult;
for (Field field : fields) {
Annotation[] ans = field.getAnnotations();
if (ans.length < 1) {
continue;
}
for (Annotation an : ans) {
if (an instanceof JavaFields) {
JavaField[] values = ((JavaFields)an).value();
for (JavaField javaField : values) {
validateResult = handleJavaField(javaField, model, field);
if (validateResult != null) {
return validateResult;
}
}
}
if (an instanceof JavaField) {
JavaField value = (JavaField)an;
validateResult = handleJavaField(value, model, field);
if (validateResult != null) {
return validateResult;
}
}
}
}
return null;
}
/**
* 处理单个字段校验注解
*
* @param javaField
*
* @param model
*
* @param field
*
* @author wengym
*
* @date 2022/12/27 11:24
*
* @return java.lang.String
*/
private static <T> String handleJavaField(JavaField javaField, T model, Field field) {
try {
// 校验器的Class
Class validatorCls = javaField.validator();
// 检验方法
Method validateMethod = validatorCls.getMethod("validateField", Object.class);
// 调用方法
field.setAccessible(true);
Object fieldValue = field.get(model);
field.setAccessible(false);
Object result = validateMethod.invoke(validatorCls.newInstance(), fieldValue);
// 结果处理
Boolean isPass = (Boolean) result;
if (!isPass) {
// 没有通过,返回错误提示信息
return javaField.message();
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
示例
model
package com.nursehealth.model.user;
import com.nursehealth.util.common.ValidateUtils.annotation.JavaField;
import com.nursehealth.util.common.ValidateUtils.validator.impl.EmptyValidator;
import com.nursehealth.util.common.ValidateUtils.validator.impl.GenderValidator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author wengym
* @version 1.0
* @desc 用户model
* @date 2022/12/19 15:52
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserModel {
/**
* 用户ID
*/
@JavaField(message = "用户ID不能为空", validator = EmptyValidator.class)
private String userId;
/**
* 性别
*/
@JavaField(message = "性别不能为空", validator = EmptyValidator.class)
@JavaField(message = "性别只能为男或女", validator = GenderValidator.class)
private String gender;
}
具体使用
调用ValidateUtils的validate静态方法校验model中的字段值是否符合规则,如果userId为空,则会返回“用户ID不能为空”,如果gender为空,则会返回“性别不能为空”,如果gender为3,则会返回“性别只能为男或女”,如果字段值符合字段校验规则,则返回null,所以可通过判断校验结果是否为null来判断校验是否通过。
public Object validate(UserModel model) {
String result = ValidateUtils.validate(model);
if (!CommonUtil.isNullStr(result)) {
return APIResponse.errorBack(result);
}
}