springboot3里实现自定义参数校验

289 阅读4分钟

参数校验

自定义注解+切面aop技术

使用自定义注解结合aop技术实现参数校验,通过把注解作为一个标记,根据标记来进行参数校验。

  1. 自定义注解
  2. 定义校验类型的枚举类
  3. 定义切面类,使用前置增强
package wnan.explore.annotations;

import wnan.explore.enumerations.ArgumentVerifyTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*****UTF-8*****
 * Description: 参数校验注解
 * Author: wnan
 * Create Date: 2024/10/8 22:47
 * Proverbs: 吃得苦中苦,方为人上人
 */
// 注解的生命周期即,注解的保留策略,注解保留在源代码、编译后的字节码文件中、运行时可以通过反射机制读取。
@Retention(RetentionPolicy.RUNTIME)
// 注解可以应用的地方 是写在方法、类、属性
@Target(ElementType.PARAMETER)
public @interface ArgumentVerify {
  boolean require() default true;
  ArgumentVerifyTypeEnum type() default ArgumentVerifyTypeEnum.OTHER;
}
package wnan.explore.enumerations;

/*****UTF-8*****
 * Description: 参数校验类型
 * Author: wnan
 * Create Date: 2024/10/8 23:02
 * Proverbs: 吃得苦中苦,方为人上人
 */
public enum ArgumentVerifyTypeEnum {
    EMAIL(1,"邮件"),PHONE(2,"电话"),OTHER(2,"其他")
    ; // 这个分号,一定要有,就算没有枚举实例也要写
    private final int type;
    private final String typeInfo;
    
    ArgumentVerifyTypeEnum(int type, String typeInfo) {
        this.type = type;
        this.typeInfo = typeInfo;
    }
    public int getType() {
        return type;
    }
    public String getTypeInfo() {
        return typeInfo;
    }
}
package wnan.explore.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import wnan.explore.annotations.ArgumentVerify;
import wnan.explore.enumerations.ArgumentVerifyTypeEnum;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;


/*****UTF-8*****
 * Description:  参数校验aop切面类
 * Author: wnan
 * Create Date: 2024/10/8 23:10
 * Proverbs: 吃得苦中苦,方为人上人
 */
@Aspect
@Component("argumentAspects")
public class ArgumentAspects {
    // 定义切点
    @Pointcut("execution(void wnan.explore.service.ArgumentVerifyServiceImpl.argumentVerifyFun1(..))")
    public void argumentToVerify() {
    }
    // 执行方法
    @Before("argumentToVerify()")
    public void verifyBeforeException(JoinPoint joinPoint) throws Exception {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 参数注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        // 参数
        Parameter[] parameters = method.getParameters();
        // 参数值
        Object[] args = joinPoint.getArgs();
       // 方法一
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                if (parameterAnnotations[i][j].annotationType().equals(ArgumentVerify.class)) {
                    // 获得具体注解方法一
//                    ArgumentVerify annotation = (ArgumentVerify) parameterAnnotations[i][j];

                    // 获得具体注解方法2
                    ArgumentVerify annotation = parameters[i].getAnnotation(ArgumentVerify.class);
                    ArgumentVerifyTypeEnum type = annotation.type();
                    switch (type) {
                        case EMAIL: {
                            System.out.println("邮件校验");
                            System.out.println(args[i]);
                            break;
                        }
                        case PHONE: {
                            System.out.println("电话校验");
                            System.out.println(args[i]);
                            break;
                        }
                        default: {
                            System.out.println("其他校验");
                            System.out.println(args[i]);
                        }
                    }
                }
            }
        }
    }
}

方法二,就是不使用if判断:

     IntStream.range(0, parameterAnnotations.length)
             .filter(i -> Arrays.stream(parameterAnnotations[i])
                     .anyMatch(annotation -> annotation.annotationType().equals(ArgumentVerify.class)))
             .forEach(i -> {
                 ArgumentVerify annotation = parameters[i].getAnnotation(ArgumentVerify.class);
                 ArgumentVerifyTypeEnum type = annotation.type();
                 // switch处理
             });      
package wnan.explore.service;

import org.springframework.stereotype.Service;
import wnan.explore.annotations.ArgumentVerify;
import wnan.explore.enumerations.ArgumentVerifyTypeEnum;

/*****UTF-8*****
 * Description: 参数校验动作类
 * Author: wnan
 * Create Date: 2024/10/10 16:24
 * Proverbs: 吃得苦中苦,方为人上人
 */
@Service
public class ArgumentVerifyServiceImpl {
    
    public void argumentVerifyFun1(@ArgumentVerify(require = true, type = ArgumentVerifyTypeEnum.OTHER) int type,
                                   String phone,
                                   @ArgumentVerify(require = true, type = ArgumentVerifyTypeEnum.EMAIL) String email) {
        System.out.println("====>>>> argumentVerifyFun1");
    }
}

测试:

@Autowired
private ArgumentVerifyServiceImpl argumentVerifyService;

@Test
void argumentVerifyFun1() {
    // aop参数校验
    argumentVerifyService.argumentVerifyFun1(1, "123 589 5621", "1235@qq.com");
} 

运行结果:

image.png

cglib动态代理+自定义注解

步骤:

  1. 自定义注解,注解用于方法参数上面
  2. 定义校验类型的枚举类
  3. 实现cglib的MethodInterceptor接口,重写intercept()方法,完成校验规则
  4. 进行cglib代理的设置。

我觉得不能使用jdk动态代理来实现,jdk代理是基于接口来实现,但是我的注解是在实现类里。

package wnan.explore.service;

import org.springframework.stereotype.Service;
import wnan.explore.annotations.ArgumentVerify;
import wnan.explore.enumerations.ArgumentVerifyTypeEnum;

/*****UTF-8*****
 * Description: 参数校验动作类
 * Author: wnan
 * Create Date: 2024/10/10 16:24
 * Proverbs: 吃得苦中苦,方为人上人
 */
@Service
public class ArgumentVerifyServiceImpl {

    public int argumentVerifyFun2(@ArgumentVerify(require = true, type = ArgumentVerifyTypeEnum.OTHER) int type,
                                  String phone,
                                  @ArgumentVerify(require = true, type = ArgumentVerifyTypeEnum.EMAIL) String email) {
        System.out.println("====>>>> argumentVerifyFun2");
        return 0;
    }
}

cglib代理

package wnan.explore.proxy;

import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/*****UTF-8*****
 * Description: cglib实现动态代理参数校验
 * Author: wnan
 * Create Date: 2024/10/11 23:23
 * Proverbs: 吃得苦中苦,方为人上人
 */
public class ArgmentVerifyCGLIB implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //        System.out.println(obj);  //不要输出 erro:java.lang.StackOverflowError 异常通常表示栈内存溢出,这通常发生在递归调用太深或者方法调用形成了无限循环时。在你提供的代码中,异常发生在 ArgmentVerifyCGLIB.intercept 方法中,特别是当你尝试打印 obj 对象时,这个对象实际上是被代理的对象。
        String name = method.getName();
        System.out.println(name);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Arrays.stream(parameterAnnotations)
            .forEach(annotations -> Arrays.stream(annotations)
                     .forEach(System.out::println));
        // 参数校验的行为在这实现,同上一个方法
        Object invoke_ = proxy.invokeSuper(obj, args);
        return invoke_;
    }

    public <T> T createProxyInstance(T t) {
        Enhancer enhancer = new Enhancer();
        // 获得aop代理后的原始类型
        Class<?> aClass = AopProxyUtils.ultimateTargetClass(t);
        // 设置代理类的父类
        enhancer.setSuperclass(aClass);
        // 当执行被代理方法时要回调的类
        enhancer.setCallback(this);
        Object o = enhancer.create();
        return (T) o;
    }
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {}

所有生成的代理方法都调用此方法而不是原始方法。可以使用 Method 对象通过正常反射调用原始方法,也可以使用 MethodProxy(更快)调用原始方法。 形参:

  • obj – “this”,增强的对象
  • method – 拦截方法
  • args – 参数数组;原始类型被包装
  • proxy ― 用于调用 super (非拦截方法);可以根据需要多次调用
@Autowired
private ArgumentVerifyServiceImpl var3; // 如果是接口类型也是没有区别,也可以实现
@Test
void t11(){
    System.out.println("Bean对应的类型"+var3.getClass());
    Class<?> aClass = AopProxyUtils.ultimateTargetClass(var3);
    System.out.println("Bean对应代理前的类型"+aClass);
    
    ArgmentVerifyCGLIB var1= new ArgmentVerifyCGLIB();
    ArgumentVerifyServiceImpl proxyInstance = var1.createProxyInstance(var3);
    proxyInstance.argumentVerifyFun2(1, "123 589 5621", "1235@qq.com");
}

运行结果: image.png

源码--->>>springboot-explore