问题描述
标准的接口开发总是离不了参数校验,Spring自然是为我们准备了相应的模板,使用@NotEmpty、@NotBlank、@NotNull等注解就可对参数进行检查校验,但这些注解必须搭配@Valid使用才能生效。具体可参考:@Valid介绍及相关注解 - 简书 (jianshu.com)。
可@Valid使用存在限制,即SpringMVC的Validation只在Controller中生效,从整个接口生态上看,这是合理的设计,数据验证都不通过,也不会到业务层了。
可如果我们需要要在业务层进行数据校验呢?且条件不仅仅是非空,还包括一些特殊的格式或范围要求,使用简单的if-else自然可以解决,但代码也随之复杂且冗余了。
解决办法
spring为我们提供的参数校验无法满足我们的需求,那就自己写注解满足自己的需求。
要求:属性上加上注解就可以实现参数校验,包括非空与指定正则校验,同时返回自定义的错误信息
自定义参数校验注解
参数校验类注解:@Validation
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 参数校验注解
* 对添加注释的属性进行非空校验与正则校验
*
* @author wyhao
* @date 2021/5/18
**/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validation {
//错误信息
String message() default "参数不能为空";
//正则表达式
String pattern() default "";
}
@Validation验证器
package com.example.springtrain.caculate.processor.serviceUtils;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Validation验证器
* 对有自定义注解@Validation的参数进行校验
*
* @author wyhao
* @date 2021/5/19
**/
@Slf4j
public class ValidationCheck {
/**
* 校验方法
* 扫描对象中的属性,查看是否有@Validation注解,有注解的进行校验
*
* @param o 要校验的对象,如入参对象
*/
public static void check(Object o) {
if (null == o) {
return;
}
Class clazz = o.getClass();
List<Field> fieldList = new ArrayList<Field>();
while (clazz != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
//变量对象的所有属性
fieldList.forEach(field -> {
field.setAccessible(true);
try {
Object value = field.get(o);
//获取属性值
Validation annotation = field.getAnnotation(Validation.class);
if (null == annotation) { //未加注解的不做处理
return;
}
checkNotNull(value, annotation);
checkPattern(value, annotation);
} catch (IllegalArgumentException | IllegalAccessException e) {
log.info("Validation验证器数据解析失败:{}", e.getMessage());
}
});
}
/**
* 非空判断
*
* @param value 属性值
* @param validation 注解信息
*/
private static void checkNotNull(Object value, Validation validation) {
//log.info("开始校验非空");
if (validation != null && value == null) {
throw new RuntimeException(validation.message());
}
}
/**
* 正则校验
*
* @param value 属性值
* @param validation 注解信息
*/
private static void checkPattern(Object value, Validation validation) {
if (null != validation.pattern() && validation.pattern().length() > 0) {//存在正则式
//将validation中给定的正则表达式编译并赋予给Pattern类
Pattern p = Pattern.compile(validation.pattern());
//以p的规则匹配value中的值
Matcher m = p.matcher(value.toString());
if (!m.matches()) { //属性值不符合正则所制定的格式,抛出异常
throw new RuntimeException(validation.message());
}
}
}
}
注解使用
在需要校验的类属性上加上校验规则:
@Data
public class Father {
@Validation(message = "姓名不可为空") //非空校验
private String name;
@Validation(pattern = "\\d+",message = "年龄只能是数字") //正则校验,前提当然也是非空
private String age;
public Father(String name, String age) {
this.name = name;
this.age = age;
}
public Father() {
}
}
首先我们的注解肯定不能影响正常的使用,下面给符合要求的数据,代码正常运行
Father father1 = new Father("Laodie","56");
ValidationCheck.check(father1); //进行参数校验
System.out.println(father1);
name为空时,下面的代码会直接抛出异常,并提示姓名不可为空
Father father2 = new Father(null,"56");
ValidationCheck.check(father2); //进行参数校验
System.out.println(father2);
age不符合必须是数字的要求时,会抛出异常并提示年龄只能是数字
Father father3 = new Father(null,"a56");
ValidationCheck.check(father3);
System.out.println(father3);
如果我们还有其他的业务需要,可以在注解中增加相应的属性,在验证器中添加自定义的规则就行,具备一定的可拓展性。
持续优化
每次使用时还需要使用ValidationCheck.check(father1)这样的代码,或许有些麻烦,因为笔者所需要的参数校验地方不多,就偷懒了,其实我们可以再写一个注解,在代码,或直接在类上使用,像这样:
//加在代码上
@ValidationCheck //进行参数校验
Father father2 = new Father(null,"56");
//或加类上
@ValidationCheck
public class ValidationTest {
@Test
public void test() {
Father father1 = new Father("Laodie","56");
System.out.println(father1);
Father father2 = new Father(null,"56");
System.out.println(father2);
Father father3 = new Father(null,"a56");
System.out.println(father3);
}
}