使用AOP和Validator技术对项目接口中的参数进行非空等校验

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

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

原文地址:https://www.cnblogs.com/yangxiaohui227/p/11177817.html