Spring Boot学习分享(九)——AOP拦截方法的参数

    在进行Web MVC开发的时候,在编写controller时总是要对请求的参数进行值的判断,最常见的情况还是判断其是否为空。虽然有一些可以用的注解可以实现判断,像是Lombok的@NotNull,SpringMVC的@RequestParam等,但总觉的不是很好用,因此萌生了自己自定义注解实现的念头。
    想到使用AOP实现起来会十分方便,因此花了点时间研究了一下。


第一步 实现对参数的拦截

1. 注解的定义
/**
 * 检验参数是否为空
 * 在方法上的注解只是校验value里面的参数是否为空
 * 在参数上的注解需要指定参数类型,才能对参数的成员进行校验
 */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckNull {
    Class<?> type() default Object.class;

    String value() default "";
}

2. AOP拦截注解

    由于AOP的@Pointcut提供了很好用的表达式,可以参考一下官方文档

    这里贴一下自己所用到的expression,注意如何AOP类和注解在同个包下面,可以不写包名,不是的话则必须指明包名。

    /**
     * 拦截注解
     */
    @Pointcut("@annotation(CheckNull) || execution(* *(@CheckNull (*),..))")
    public void check() {
    }
    

第二步 得到参数并处理

    通过上面定义的切口,现在已经能够得到参数了,接下来只要拿到参数就可以了。这里使用的是AOP的aroundresolveMethod等函数是我自定义的处理函数,文章最后会贴完整的代码。
    这个地方需要注意的是ProceedingJoinPoint中方法的注解和参数的注解获取有点坑,具体的可以看代码了解一下。

    @Around(value = "check()")
    public Object around(ProceedingJoinPoint joinPoinresolveMethodt) throws Throwable {

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //得到真实的方法对象
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                method.getParameterTypes());
        //拿到方法的注解
        Annotation[] annotations = realMethod.getDeclaredAnnotations();
        //得到参数列表
        Object[] args = joinPoint.getArgs();
        //得到参数名列表
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //先校验方法上的注解
        String r1 = checkMethod(annotations, args, parameterNames);
        if (null == r1) {
            //再校验参数上的注解
            r1 = resolveParam(args, realMethod.getParameterAnnotations());
            if (null == r1) {
            	// 没有错误,继续执行应当执行的函数
                return joinPoint.proceed();
            }
        }
        //适用于返回值为json的controller
        return ResultUtil.error(ExceptionEnum.PARAMETER_NULL.getCode(), String.format(ExceptionEnum.PARAMETER_NULL.getMsg(), r1));
    }

完整代码

    该代码只是实现了对函数的参数的判空处理,如果要判断的参数为实体类中的某个属性,则必须在方法的参数上使用注解,并标注实体类的类型。

@Aspect
@Log4j2
public class CheckNullAop {
    /**
     * 拦截方法上已经参数上的注解
     */
    @Pointcut("@annotation(CheckNull) || execution(* *(@CheckNull (*),..))")
    public void check() {
    }

    /**
     * 方法参数校验
     *
     * @param annotations    方法上的注解数组
     * @param args           方法的参数值列表
     * @param parameterNames 方法的参数名列表
     */
    private String checkMethod(Annotation[] annotations, Object[] args, String[] parameterNames) {

        //校验方法上的注解
        //考虑到有多个相同的注解
        for (Annotation annotation : annotations) {
            if (annotation instanceof CheckNull) {
                //先解析方法上的参数
                String result = resolveMethod((CheckNull) annotation, args, Arrays.asList(parameterNames));
                if (null != result)
                    return result;
            }
        }
        return null;
    }

    /**
     * 解析方法上的注解
     *
     * @param checkNull  注解
     * @param args       方法的参数值列表
     * @param parameters 方法的参数名列表
     */
    private String resolveMethod(CheckNull checkNull, Object[] args, List<String> parameters) {

        assert args.length == parameters.size();

        String[] list = checkNull.value().split(",");

        //直接遍历value
        for (String s : list) {
            int index = parameters.indexOf(s);
            if (-1 != index && null == args[index]) {
                return s;
            }
        }

        return null;
    }

    /**
     * 解析参数上的注解
     *
     * @param args       方法的参数值列表
     * @param parameters 参数的注解列表,为二维数组
     */
    private String resolveParam(Object[] args, Annotation[][] parameters) {

        for (int i = 0; i < parameters.length; ++i) {
            for (Annotation annotation : parameters[i]) {
                if (annotation instanceof CheckNull) {
                    CheckNull c = (CheckNull) annotation;
                    if (c.type() != Object.class) {
                        for (String s : c.value().split(",")) {
                            try {
                                //拿到不能访问的属性
                                //无法拿到父类属性
                                Field field = args[i].getClass().getDeclaredField(s);
                                if (!field.isAccessible()) field.setAccessible(true);
                                if (null == field.get(args[i])) {
                                    return s;
                                }
                            } catch (NoSuchFieldException | IllegalAccessException e) {
                                log.warn(e.getMessage());
                            }
                        }
                    }
                    break;
                }
            }
        }

        return null;
    }

    @Around(value = "check()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //得到真实的方法对象
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                method.getParameterTypes());
        //拿到方法的注解
        Annotation[] annotations = realMethod.getDeclaredAnnotations();
        //得到参数列表
        Object[] args = joinPoint.getArgs();
        //得到参数名列表
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //先校验方法上的注解
        String r1 = checkMethod(annotations, args, parameterNames);
        if (null == r1) {
            //再校验参数上的注解
            r1 = resolveParam(args, realMethod.getParameterAnnotations());
            if (null == r1) {
                // 没有错误,继续进行
                return joinPoint.proceed();
            }
        }
        //适用于返回值为json
        return ResultUtil.error(ExceptionEnum.PARAMETER_NULL.getCode(), String.format(ExceptionEnum.PARAMETER_NULL.getMsg(), r1));
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值