使用AOP和Validator技术对项目接口中的参数进行非空等校验
阅读原文时间:2023年07月12日阅读:2

javax.validation.Validator基础知识补充:
validator用来校验注解的生效,如:
@NotBlank(message = "地址名不能为空")
private String addressName;
当addressName这个值为null时,会报message = "地址名不能为空"的值:
简单demo:
//对Location对象进行校验
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
//因为:Location可能有很多字段都需要校验,所以校验后返回的信息是一个集合
Set> sets = validator.validate(location);
//校验不通过的信息都存到了set集合中,从中可以拿到校验不通过的信息
if(null!=sets && sets.size()>1){
for (ConstraintViolation set : sets) {
String message = set.getMessage();//注解的message信息
System.out.println(message);
}
}
//所有的校验注解在javax.validation.constraints这个包下

如包下的NotNull注解,源码如下:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotNull.List.class)
@Documented
@Constraint(
validatedBy = {}
)
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";

Class<?>\[\] groups() default {};

Class<? extends Payload>\[\] payload() default {};

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION\_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE\_USE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface List {  
    NotNull\[\] value();  
}  

}
//所以,我们也可以自定义注解

//例如:我们的接口只对VIP为白银的用户可用,自定义注解如下
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {CheckVIPValidator.class} //注解要生效,就要有一个类的方法让它生效
)
public @interface ViP {

    String message() default "只有白银用户才能使用";

    Class<?>\[\] groups() default {};

    Class<? extends Payload>\[\] payload() default {};

    String value();

}
//使注解生效的类:
public class CheckVIPValidator implements ConstraintValidator {
private String vip=null;
@Override
public void initialize(ViP constraintAnnotation) {
vip=constraintAnnotation.value();
}

@Override  
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {  
    if(s.equals(vip)){  
        return true;  
    }  
    return false;  
}  

}

//下面是APO结合validator技术,本项目是基于springboot的一个简单项目,spring项目也可以:

先看项目结构:

各对象的数据:

自定义注解和其实现类:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {CheckVIPValidator.class} //注解要生效,就要有一个类的方法让它生效
)
public @interface ViP {

    String message() default "只有白银用户才能使用";

    Class<?>\[\] groups() default {};

    Class<? extends Payload>\[\] payload() default {};

    String value();

}
public class CheckVIPValidator implements ConstraintValidator {
private String vip=null;
@Override
public void initialize(ViP constraintAnnotation) {
vip=constraintAnnotation.value();
}

@Override  
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {  
    if(s.equals(vip)){  
        return true;  
    }  
    return false;  
}  

}
//接口

@Controller
public class ValidatorController {

@RequestMapping(value = "check/validator",method = RequestMethod.POST)  
@ResponseBody  
public CommonResponseDTO checkValidator(@RequestBody User user) throws Exception {  
    CommonResponseDTO beeboxCommonResponseDTO = new CommonResponseDTO();  
    return beeboxCommonResponseDTO;  
}  

}

//请求对象:
public class User {
@NotNull(message = "名字不能为空")
private String name;
@ViP(value ="白银",message ="只有白银用户才能访问")
private String vip;
@Min(value = 18,message = "年龄需要大于18岁")
  @NotNull(message="年龄不能为空")
private Integer age;

public String getName() {  
    return name;  
}

public void setName(String name) {  
    this.name = name;  
}

public String getVip() {  
    return vip;  
}

public void setVip(String vip) {  
    this.vip = vip;  
}  

}
//返回对象:
public class CommonResponseDTO {
private String code;
private String desc;

public String getCode() {  
    return code;  
}

public void setCode(String code) {  
    this.code = code;  
}

public String getDesc() {  
    return desc;  
}

public void setDesc(String desc) {  
    this.desc = desc;  
}  

}
//最重要的对象:

切面类:
package com.example.demo.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

/**
* 场景:使用AOP技术对接口(controller下的方法参数进行校验,对贴有校验注解如@NotNull @Min等做校验,不通过的话,就返回对应错误码和描述)
* 按照项目规范:请求参数只能是一个值,返回参数必须含有一个错误码字段和一个错误码描述字段
*
*
*
*
*/

@Component
@Aspect
@Order(1) //当有多个切面时,加上这个顺序,就可以控制前后顺序了
public class AOPAspect {

/\*\*  
 \* //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点 \* com.sample.service.impl..\*.\*(..)  
 \* 整个表达式可以分为五个部分:  
 \* 1、execution(): 表达式主体。  
 \* 2、第一个\*号:表示返回类型,\*号表示所有的类型。  
 \* 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。  
 \* 4、第二个\*号:表示类名,\*号表示所有的类。  
 \* 5、\*(..):最后这个星号表示方法名,\*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。  
 \*/

@Pointcut("execution(\* com.example.demo.controller.\*.\*(..))")  
public void declareJoinPointerExpression() {}

/\*\*  
 \* ProceedingJoinPoint 是 JoinPoint的子接口,有2个方法  
 \* Object proceed() throws Throwable //执行目标方法  
   Object proceed(Object\[\] var1) throws Throwable //传入的新的参数去执行目标方法  
 \*  
 \* JoinPoint的几个参数:  
 \* Signature getSignature(); //封装了方法相关的信息  
 \*      signature.getDeclaringType();//方法所在的字节码对象  
 \*      signature.getDeclaringTypeName(); 方法所在的类名(包.类这种形式)  
 \*      signature.getModifiers();//获取方法的修饰符的数字表示,如public修饰符返回1,private...  
 \*      signature.getName();//获取方法的名称  
 \*      //Signature的实现类是MethodSignature 可以获得方法的相关数据,如参数,返回值类型,方法等  
 \*       MethodSignature signature = (MethodSignature)joinPoint.getSignature();  
         Method method = signature.getMethod();//获取方法  
         Class returnType = signature.getReturnType();//获取返回值的类型  
 \*joinPoint.getArgs();//获取方法的参数  
 \*  
 \* joinPoint.getKind();//返回的是JoinPoint对象里面的常量,如method-execution(方法的执行)  
 \* joinPoint.getStaticPart();//获取的是JoinPoint里面的一个内部接口  
 \* joinPoint.getTarget(); //获取被代理对象  
 \* joinPoint.getThis(); //获取的是代理对象  
 \*  
 \* @param joinPoint  
 \*/  
@Before("declareJoinPointerExpression()")  
public void beforeMethod(JoinPoint joinPoint){  
    System.out.println("增强前");  
    joinPoint.getThis();

}  
@After("declareJoinPointerExpression()")  
public void AferMethod(JoinPoint joinPoint){  
    System.out.println("增强后...");  
}  
//本次校验的场景是contorller的方法只有一个参数,并且放回值都有状态和描述2个字段  
@Around("declareJoinPointerExpression()")  
public Object aroundMethod(JoinPoint joinPoint) throws Throwable {  
    MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //通过该对象可以获取方法返回值  
    Class returnType = signature.getReturnType();//方法返回类型  
    Object\[\] args = joinPoint.getArgs(); //本次实验只能有一个请求参数  
    if(null==args || args.length!=1){  
      return getReturnObject(returnType, "400", "参数数目不对");  
    }  
    Object arg = args\[0\];//获取请求参数  
    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();  
    Validator validator = validatorFactory.getValidator();  
    Set<ConstraintViolation> violations = validate(validator, arg);//校验参数,异常会封装到set集合  
    StringBuilder sb = new StringBuilder();//拼接异常数据  
    if(null!=violations || violations.size()>1){  
        sb.append("基础数据校验不通过:");  
        for (ConstraintViolation set : violations) {  
            String message = set.getMessage(); //异常数据信息  
            sb.append(message).append("</br>");  
        }  
       return getReturnObject(returnType,"400",sb.toString());

    }else {

        ProceedingJoinPoint pjp= (ProceedingJoinPoint) joinPoint; //如果没有异常数据,就执行原来的方法  
        Object proceed = pjp.proceed();//执行原来方法  
        return proceed;  
    }

}

private Set<ConstraintViolation> validate(Validator validator,Object object,Class ... groups){  
    Set constraintViolations = validator.validate(object, groups);//校验不通过的数据会封装到set集合  
    return constraintViolations;  
}

/\*\*  
 \*  
 \* @param clz 方法返回值的字节码对象  
 \* @param errorCode 错误码  
 \* @param errorMessage 错误信息  
 \* @param <T> 返回值类型  
 \* @return  
 \*/  
private <T> T getReturnObject(Class<T> clz,String errorCode,String errorMessage) {  
    if(clz.getName().equals("void")){ //没有返回值时  
        return null;  
    }  
    T t = null;  
    try {  
        t = clz.newInstance();  
    } catch (InstantiationException e) {  
        e.printStackTrace();  
    } catch (IllegalAccessException e) {  
        e.printStackTrace();  
    }  
    setFiledValue("code",errorCode,t); //设置错误码  
    setFiledValue("desc",errorMessage,t); //设置错误信息  
    return t;

}

/\*\*  
 \* 调用setXX方法设置属性  
 \* @param fileName  
 \* @param value  
 \* @param obj  
 \*/  
private void setFiledValue(String fileName,Object value,Object obj)  {  
    //该方式找不到对应字段不会报错  
    if(StringUtils.isEmpty(fileName)){  
        return;  
    }  
    //转成SetXXX方法名  
    char\[\] chars = fileName.toCharArray();  
    chars\[0\]=(char) (chars\[0\]-32); //首字母转大写  
    //拼接成方法名  
    String methodName="set"+String.copyValueOf(chars);  
    Class<?> clz = obj.getClass();  
    Method\[\] declaredMethods = clz.getDeclaredMethods();  
    for (Method method : declaredMethods) {  
        String name = method.getName();  
        boolean equals = method.getName().equals(methodName);  
        if(equals){  
            try {  
                method.invoke(obj,value);  
            } catch (IllegalAccessException e) {  
                e.printStackTrace();  
            } catch (InvocationTargetException e) {  
                e.printStackTrace();  
            }  
        }

    }

}

}

//接口调用后结果如下: