以注册功能为例
@RestController
@RequestMapping("validater")
public class ValidateController {
@ValidateGroup(fields = {
@ValidateField(index = 0,notNull = true,maxLen = 10,code = "param1-error",message = "param1校验错误"),
@ValidateField(index = 1,notNull = true,fieldName = "passWord",minLen = 6,code = "passWord-erro",message = "密码校验错误"),
@ValidateField(index = 1,notNull = true,fieldName = "age",minVal = 0,code = "age-error",message = "年龄不能小于0"),
@ValidateField(index = 1,notNull = true,fieldName = "tall",minVal = 0,maxVal = 250.9,code ="tall-error",message = "身高范围出错"),
@ValidateField(index = 1,notNull = true,fieldName = "phone",regStr = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",code = "phone-error",message = "手机号错误")
})
@PostMapping("post")
public String postValidater(@RequestParam String param1, RegisterDto dto){
System.out.println("成功通过校验");
System.out.println("第一个参数是:" + param1);
System.out.println("第二个参数是"+dto.toString());
return "succeed";
}
}
其中:请求Dto包含name、passWord、phone等字段;
利用AspectJ对接口方法直接进行代理校验
@ValidateGroup
标注的方法,而这个Group注解中的属性就是@ValidateField []
@ValidateField
数组后,遍历。通过比对注解的参数 与 dto或者param中对应名字的参数来进行校验经过流程分析可知:至少需要以下几个方法(并可以抽取为公共组件)
public Method getMethod(ProceedingJoinPoint pjp)
:获取被AOP拦截的方法Method对象 public Annotation getAnnotationByMethod(Method method, Class annoClass)
:获取目标方法对象的指定注解对象public Object getFieldFromDtoByFieldName(Object dto , String fieldName)
从dto中,获取指定属性名的属性值如下工具类也可以做成全静态方法
public class AopUtils {
private volatile static AopUtils aopUtils;
private AopUtils() {
}
public static AopUtils getInstance() {
if (aopUtils == null) {
synchronized (AopUtils.class) {
if (aopUtils == null) {
aopUtils = new AopUtils();
}
}
}
return aopUtils;
}
/**
* 获取目标类的指定方法
*/
public Method getMethodByClassAndName(Class c, String methodName) {
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
return method;
}
}
return null;
}
/**
* 获取目标方法的指定注解
* 相当于 method.getAnnotation(xxxx.class);
*/
public Annotation getAnnotationByMethod(Method method, Class annoClass) {
Annotation all[] = method.getAnnotations();
for (Annotation annotation : all) {
if (annotation.annotationType() == annoClass) {
return annotation;
}
}
return null;
}
/**
* 获取被拦截方法的对象
* 配合使用,最终用于在Aspectj中获取被拦截方法上的注解
* 例如:AopUtils.getMethod(pjp).getDeclaredAnnotation(被aop拦截的注解.class)
*/
public Method getMethod(ProceedingJoinPoint pjp) {
//获取参数的签名
MethodSignature msig = (MethodSignature) pjp.getSignature();
// MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 如果在实现类的方法上,应该使用反射获取当前对象的方法对象
Object target = pjp.getTarget();//获取连接点所在的目标对象(被代理的对象)而不是父类or接口
//方法名 + 方法形参 ————》获取指定的方法对象(重载)
String methodName = msig.getName();
Class[] parameterTypes = msig.getParameterTypes();
Method method = null;
try {
method = target.getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
//log.error(...);
}
return method;
}
/**
* 从dto中,获取指定属性名的属性值;
*/
public Object getFieldFromDtoByFieldName(Object dto , String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class> dtoClazz = dto.getClass();
Field field = dtoClazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(dto);
}
// 这个其实还有另一种写法
// private Method getMethod(ProceedingJoinPoint joinPoint) {
// try {
// Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
// return joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), parameterTypes);
// } catch (NoSuchMethodException e) {
// e.printStackTrace();
// }
// return null;
// }
}
这个注解用于被AspectJ拦截,其属性是一个数组,用于参数校验
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ValidateGroup {
ValidateFiled[] fields();
}
public void register(@RequestParam String param1 , @RequestBody Dto dto){}
中应该设置index = 1 ,这是由于joinPoint.getArgs()
获取的形参是一个数组,需要用index指定位置如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ValidateField {
/**
* 参数索引位置:接口中有多个参数时用index指定需要校验的参数
* 默认:0号索引,当且仅当接口方法只有一个参数
*/
int index() default 0;
/**
* 默认:如果参数是基本数据类型或String,就不用指定该参数
* 如果参数是对象,要验证对象里面某个属性,就用该参数指定属性名
*/
String fieldName() default "";
/**
* 错误码,用于日志记录
*/
String code() default "";
/**
* 错误提示语,用于日志记录
*/
String message() default "";
/**
* 正则验证
*/
String regStr() default "";
/**
* 非空校验,为true表示不能为空,false表示能够为空
*/
boolean notNull() default false;
/**
* 字符串最大长度
*/
int maxLen() default 0x7fffffff;
/**
* 字符串最小长度
*/
int minLen() default 0;
/**
* 最大值,用于验证数值类型数据
*/
double maxVal() default 0x1.fffffffffffffP+1023;
/**
* 最小值,用于验证数值类型数据
*/
double minVal() default 0x0.0000000000001P-1022;
}
@Component
@Aspect
public class ValidateAspectHandler {
private AopUtils aopUtils = AopUtils.getInstance();
/**
* 使用AOP对使用了ValidateGroup的方法进行代理校验
*/
@Around("@annotation(ValidateGroup)")
public Object validateAround(ProceedingJoinPoint joinPoint) throws Throwable {
//1.获取当前方法对象method
//2.根据method对象获取对应的ValidateGroup对象
//3.调用封装方法,将validateGroup.fields() 与 joinPoint.getArgs()形参数组传入,进行校验
//4.如果为true,则执行下一步
return joinPoint.proceed();
}
/**
* 封装方法:验证参数是否合法
*/
private boolean validateField(ValidateFiled[] validateFields, Object[] args) {
for (ValidateFiled validateFiled : validateFields) {
//1.每次循环都是一个校验逻辑
//2.index仍然是指定对第几号元素进行校验
//3.fieldName如果不指定,那就是对基本数据类型即@RequestParam进行校验;如果指定,则对Dto即@RequestBody进行校验
}
@Component
@Aspect
public class ValidateAspctJHandler {
// private static org.slf4j.Logger logger = LoggerFactory.getLogger("ValidateAspctJHandler");
private AopUtils aopUtils = AopUtils.getInstance();
/**
* 使用AOP对使用了ValidateGroup的方法进行代理校验
*/
//相当于用@Around + return joinPoint.proceed();
//使用@Before的时候不能用ProceedingJoinPoint 只能用JoinPoint
@Before("@annotation(ValidateGroup)")
public void validateAround(JoinPoint joinPoint) throws Throwable {
//获取被代理的方法对象
Method method = aopUtils.getMethod(joinPoint);
//获取被代理的方法对象对应的@ValidateGroup对象
ValidateGroup validateGroup = (ValidateGroup)aopUtils.getAnnotationByMethod(method, ValidateGroup.class);
//获取被代理方法的参数数组(这是参数值,而不是 method.getParameterTypes()返回的是Class[] )
Object[] args = joinPoint.getArgs();
/*
* args和validateGroup中包含了全部需要校验的信息,因此可以封装为一个方法
* 在这个方法中,如果校验失败则用throws抛异常的方式终止
*/
validateAllFields(validateGroup.fields(), args);
}
/**
* 验证参数是否合法
* ValidateField[]中每一条都是一个校验规则,每一条都对应一个属性
* Object[] args中是所有的请求参数值,需要从args[validateFiled.index()中确定是对谁进行校验
*/
private void validateAllFields(ValidateField[] validateFields, Object[] args) throws NoSuchFieldException, IllegalAccessException {
//遍历:对每个@ValidateField进行校验
for (ValidateField validateFiled : validateFields) {
Object arg;
//1.当fieldName为默认值""的时候,此时是对@RequestParam即基本数据类型orString进行校验
if ("".equals(validateFiled.fieldName())) {
//arg是基本数据类型orString
arg = args[validateFiled.index()];
//2.如果fieldName设置了,那就是对dto中的某个属性进行校验
} else {
//获取第index号参数dto指定的属性值
arg = aopUtils.getFieldFromDtoByFieldName(args[validateFiled.index()], validateFiled.fieldName());
}
//3.以下是校验流程,需要同时考虑是对dto属性or基本数据类型orString
//3.1判断参数是否为空
if (validateFiled.notNull()) {
if (arg == null || arg.equals("")) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
//如果该参数能够为空,并且当参数为空时,就不用判断后面的了 ,直接返回
} }else {
if (arg == null || arg.equals("")) {
return;
}
}
//3.2判断字符串最大长度 如果设置为一个负数则不校验 默认为最大int值
if (validateFiled.maxLen() >= 0) {
if (arg.toString().length() > validateFiled.maxLen()) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
}
}
//3.3判断字符串最小长度 如果设置为一个负数则不校验 默认为0
if (validateFiled.minLen() >= 0) {
if (arg.toString().length() < validateFiled.minLen()) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
}
}
//3.4判断数值最大值 当不是默认值0x1.fffffffffffffP+1023的时候进行判断
if (validateFiled.maxVal() != 0x1.fffffffffffffP+1023) {
if (Double.parseDouble(arg.toString()) > validateFiled.maxVal()) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
}
}
//3.5判断数值最小值 当不是默认值0x0.0000000000001P-1022的时候进行判断
if (validateFiled.minVal() != 0x0.0000000000001P-1022) {
if (Double.parseDouble(arg.toString()) < validateFiled.minVal()) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
}
}
//3.6判断正则 若未设置正则校验则跳过
if (!"".equals(validateFiled.regStr())) {
if (arg instanceof String || arg instanceof Integer || arg instanceof BigDecimal || arg instanceof Double) {
if (!(arg.toString()).matches(validateFiled.regStr())) {
// logger.error(validateFiled.code() + ":" + validateFiled.message());
throw new RuntimeException(validateFiled.code() + ":" + validateFiled.message());
}
}
}
}
return;
}
}
@RestController
@RequestMapping("validater")
public class ValidateController {
@ValidateGroup(fields = {
//如果是index=0也可以省略不写
@ValidateField(index = 0,notNull = true,maxLen = 10,code = "param1-error",message = "param1校验错误"),
@ValidateField(index = 1,notNull = true,fieldName = "passWord",minLen = 6,code = "passWord-erro",message = "密码校验错误"),
@ValidateField(index = 1,notNull = true,fieldName = "age",minVal = 0,code = "age-error",message = "年龄不能小于0"),
@ValidateField(index = 1,notNull = true,fieldName = "tall",minVal = 0,maxVal = 250.9,code ="tall-error",message = "身高范围出错"),
@ValidateField(index = 1,notNull = true,fieldName = "phone",regStr = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",code = "phone-error",message = "手机号错误")
})
@PostMapping("post")
public String postValidater(@RequestParam String param1, RegisterDto dto){
System.out.println("成功通过校验");
System.out.println("第一个参数是:" + param1);
System.out.println("第二个参数是"+dto.toString());
return "succeed";
}
}
如果参数错误,则会直接抛异常终止请求
如果参数都正确就可以通过校验,如下: