前言
参数校验,相信每个后端开发人员都有接触。一般情况下前后端都会对数据进行双重校验,保证正确性。比如某个参数不可为null,手机号格式不对,等等。
本文的重点在接口级别校验,起始于工作中的微服务接口参数检查。大脑最省力原则告诉我,能不一个一个手动写,咱就坚持抽象出来。
通常采用的是JSR303规范来做校验,Hibernate validator是JSR303规范的一种很好的实现。
依赖引入(Maven)
略(......)见谅
接口返回
抽象,那么执行接口方法之前要check参数,不符合场景直接返回结果。首先,我们抽象一个接口返回包装类。相信这个已经不稀奇,即使没有参数校验,大家N年前就已经做了。
public class MessageReturn {
/** 状态:默认0 成功;其他为失败 */
private int status;
/** 响应结果*/
private T result;
/** 相应描述*/
private String message;
// 余下部分已省略
}
自定义注解
AOP切点
/**
* 参数校验注解类
*
* @author wangzhuhua
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Order(AspectOrderConstant.Validator)
@Documented
@Inherited
public @interface HValidate {
/**默认错误码*/
// 这个default 是上面的MessageReturn
int errorCode() default MessageUtil.BUSY;
}
校验分组
这里自定义了分组校验规则
/**
* 参数校验分组注解类
*
* @author wangzhuhua
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Constraint(validatedBy = HValidateGroup.HValidateGroupValidator.class)
@Documented
public @interface HValidateGroup {
//默认错误消息
String message() default "";
//分组
Class>[] groups() default { };
//负载
Class extends Payload>[] payload() default { };
//校验分组
Class>[] value() default {};
class HValidateGroupValidator implements ConstraintValidator {
private Class>[] groups;
private ValidatorFactory validatorFactory;
public void initialize(HValidateGroup hValidateGroup) {
this.groups = hValidateGroup.value();
validatorFactory = ValidatorInterceptor.ValidatorFactory;
}
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
if (object == null) {
return true;
}
Set> validResult = validatorFactory.getValidator().validate(object, groups);
if(validResult.size() == 0){
return true;
}
Iterator> iterator = validResult.iterator();
while(iterator.hasNext()){
ConstraintViolation violation = iterator.next();
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(violation.getMessageTemplate())
.addPropertyNode(violation.getPropertyPath().toString())
.addConstraintViolation();
}
return false;
}
}
}
AOP拦截校验
/**
* 参数校验配置
*
* @author wangzhuhua
**/
@Component
@Aspect
public class ValidatorInterceptor {
/**
* true:快速校验模式,false:全部校验模式
*/
@Value("${hibernate.validator.failFast:false}")
private boolean validateModel;
/**
* 解决组合切点参数注入只适用于切点级别高的效应
*/
ThreadLocal currentAnnatation = new ThreadLocal<>();
/**
* 校验器工厂
*/
private ValidatorFactory validatorFactory;
static ValidatorFactory ValidatorFactory;
/**
* 构建校验工厂
*/
@PostConstruct
public void initValidator() {
validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("MyMessages")))
.failFast(validateModel)
.buildValidatorFactory();
ValidatorInterceptor.ValidatorFactory = validatorFactory;
}
/**
* 切点
*/
@Pointcut("@within(com.sstc.hmis.util.validator.HValidate)")
public void pointcutWithin() {
}
/**
* 切点
*/
@Pointcut("@annotation(com.sstc.hmis.util.validator.HValidate)")
public void pointcutAnnotation() {
}
/**
*
* @param proceedingJoinPoint
* @param hValidate
* @return
*/
@Around(value = "pointcutWithin() && @within(hValidate)")
public Object pointcutWithin(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
setCurrentAnnatation(hValidate);
return process(proceedingJoinPoint);
}
/**
*
* @param proceedingJoinPoint
* @param hValidate
* @return
*/
@Around(value = "pointcutAnnotation() && @annotation(hValidate)")
public Object pointcutAnnotation(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
setCurrentAnnatation(hValidate);
return process(proceedingJoinPoint);
}
/**
* 校验切点
*
* @param proceedingJoinPoint 切点对象
* @return 接口响应
* @throws Throwable
*/
@Around("pointcutWithin()")
public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 获得的方法
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
// 获得切入目标对象
Object target = proceedingJoinPoint.getThis();
// 获得切入方法参数
Object[] args = proceedingJoinPoint.getArgs();
// 执行验证
Set> validResult = validateParameters(target, method, args);
// 参数不合法返回
if (validResult.size() > 0) {
return processConstraintViolations(validResult);
}
return proceedingJoinPoint.proceed();
}
/**
* 校验方法参数
*
* @param target 校验方法所在对象
* @param method 将校验的方法
* @param args 参数
* @return 校验结果
*/
public Set> validateParameters(Object target, Method method, Object[] args) {
// 校验结果
Set> result = new HashSet<>();
// 先验证默认Validator校验范围
Validator validator = validatorFactory.getValidator();
ExecutableValidator executableValidator = validator.forExecutables();
Set> validResult = executableValidator.validateParameters(target, method, args);
result.addAll(validResult);
return result;
}
/**
* 验证信息转MessageReturn
*
* @param violations 验证信息
* @return 返回消息
*/
public MessageReturn
使用示例
接口定义
实际工作中使用到Spring Cloud,就用这个做个示例了
@RequestMapping(value = "/add", method = RequestMethod.POST)
MessageReturn add(@RequestBody @HValidateGroup({CustomObjectGroup.Add.class }) CustomObject customObject);
接口实现类
@RestController
// 这里作为切点进入
@HValidate(errorCode = 10101000)
// 下面这个是未知异常catch,请忽略
@ErrorHandler(errorCode = 20101099)
public class CustomServiceImpl implements CustomService {
@Override
// @HValidate(errorCode = 10101099)
// 有需要的场景,这里的级别更高,出现不同的错误码
public MessageReturn add(@RequestBody CustomObject customObject) {
// do something
return xxxx;
}
校验规则
/**
* Demo
*
* @author wangzhuhua
**/
public class CustomObject {
/** 标识 */
@NotEmpty(message = "ID不可为空", groups = { CustomObjectGroup.Update.class })
private String id;
/** 名称 */
@NotEmpty(message = "名称不能为空", groups = { CustomObjectGroup.Add.class,
CustomObjectGroup.Update.class })
private String name;
}
这样拓展了Hibernate validator,用起来方便,即可分组,也可自定义校验规则。