全局校验注解实现

全局校验注解实现,需要思考的问题

  • 1、aop基于实现动态代理思想解读
  • 2、反射对私有属性的暴力破解
  • 3、JDK8 对重复注解的支持策略

首先还是看现实的效果

    @RequestMapping(value = Path.ADD_FILTER)
    @ParamObjVerify(verifyKey = "filterConfig.filterName", paramRule = @ParamVerify(isNotBlank = true))
    @ParamObjVerify(verifyKey = "filterConfig.path", paramRule = @ParamVerify(isNotBlank = true))
    @ParamObjVerify(verifyKey = "filterConfig.desc", paramRule = @ParamVerify(isNotBlank = true))
    @ParamObjVerify(verifyKey = "filterConfig.isGlobal", paramRule = @ParamVerify(isNotBlank = true))
    @ParamObjVerify(verifyKey = "filterConfig.isExist", paramRule = @ParamVerify(isNotBlank = true))
    public ResultMsg addFilter(FilterConfig filterConfig, @ParamVerify(isNotBlank = true) String name) throws Exception {
        try {
            filterConfigService.addFilter(filterConfig);
        } catch (Exception e) {
            logger.error("addIpSet is error", e);
            throw e;
        }
        return new ResultMsg("添加ipSet数据成功");
    }

全局校验注解分为两种模式(1、方法级别校验–应对obj类型入参 2、参数级别校验–对应简单类型属性校验)
上图代码表示对filterConfig对象的isGlobal等属性 及 name做非空校验。

回归来看实现

首先,我们需要动态代理的设计模式思想(用前置代理与后置代理包裹每个需要校验的函数) 由此分析–得出我们的工程是基于spring boot的,所以想到需要aop。下面上代码。

1、我们先定义自己的注解

package com.weibo.jerryweb.verify;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * "参数不能为空"注解,作用于方法参数上。
 *
 * @author yangjian9
 * @date Created on 2018/3/27
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamVerify {
    // 是否非空,默认不能为空
    boolean isNotNull() default false;
    // 不为空,不为空字符
    boolean isNotBlank() default false;
    // 是否是有效ip地址
    boolean isIp() default false;
    // 是否是有效ip地址段
    boolean isIps() default false;
    // 是否是纯数字
    boolean isNumber() default false;
    // 最大长度
    int maxLen() default Integer.MAX_VALUE;
    // 最小长度
    int minLen() default -1;
}
package com.weibo.jerryweb.verify;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * jdk8支持重复注解
 *
 * @author yangjian9
 * @date Created on 2018/3/28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamObjVerifys {
    ParamObjVerify[] value();
}
package com.weibo.jerryweb.verify;

import org.apache.commons.lang.ObjectUtils;

import java.lang.annotation.*;


/**
 * 对象注解,作用于方法上。
 *
 * @author yangjian9
 * @date Created on 2018/3/28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ParamObjVerifys.class)
public @interface ParamObjVerify {
    // 对象名称.对象属性
    String verifyKey() default "";
    ParamVerify paramRule();
}

可以看出
ParamVerify是基础注解(1、用于参数级别 2、作为ParamObjVerify的依赖核)
ParamObjVerifys 是对ParamObjVerify的集合(JDK8 通过这种嵌套方式支持了重复注解)

2、注解有了,我们需要定义切面 与 切面拦截方法的实现。 上代码,代码里有具体注释说明

package com.weibo.jerryweb.verify.verifyaop;

import com.weibo.jerrycore.ConfigServerException;
import com.weibo.jerrycore.ConfigServerExceptionFactor;
import com.weibo.jerryweb.utlis.RegexUtil;
import com.weibo.jerryweb.verify.ParamObjVerify;
import com.weibo.jerryweb.verify.ParamObjVerifys;
import com.weibo.jerryweb.verify.ParamVerify;
import org.apache.commons.lang.StringUtils;
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 java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yangjian9
 * @date Created on 2018/3/27
 */
@Component
@Aspect
public class ConfigAop {
    /**
     * 定义有一个切入点,范围为web包下的类
     */
    @Pointcut("execution(public * com.weibo.jerryweb.controller.*.*(..))")
    public void verifyParam() {
    }

    @Before("verifyParam()")
    public void doBefore(JoinPoint joinPoint) {
    }

    /**
     * 检查参数是否为空
     */
    @Around("verifyParam()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = ((MethodSignature) pjp.getSignature());
        //得到拦截的方法
        Method method = signature.getMethod();

        //获取方法参数名
        String[] paramNames = signature.getParameterNames();
        //获取参数值
        Object[] paranValues = pjp.getArgs();
        // 组装参数名称与参数值到集合
        Map<String, Object> paramMap = new HashMap<String, Object>();
        for (int i = 0; i < paramNames.length; i++) {
            paramMap.put(paramNames[i], paranValues[i]);
        }

        // 仅获取当前方法上的注册信息
        Annotation[] methodAnnotations = method.getDeclaredAnnotations();
        if (methodAnnotations == null || methodAnnotations.length == 0) {
            return pjp.proceed();
        }
        // 方法力度注解规则校验
        methodAnnotationVerify(methodAnnotations, paramMap);

        //获取方法参数注解,返回二维数组是因为某些参数可能存在多个注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return pjp.proceed();
        }
        // 参数力度注解规则校验
        paramAnnotationVerify(parameterAnnotations, paramNames, paranValues);
        return pjp.proceed();
    }

    /**
     * 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
     *
     * @param joinPoint
     */
    @AfterReturning("verifyParam()")
    public void doAfterReturning(JoinPoint joinPoint) {
    }

    /**
     * 参数级别校验注解
     *
     * @param parameterAnnotations
     * @param paramNames
     * @param paramValues
     */
    private void paramAnnotationVerify(Annotation[][] parameterAnnotations, String[] paramNames, Object[] paramValues) {
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                if (parameterAnnotations[i][j] == null) {
                    break;
                }
                if (parameterAnnotations[i][j] instanceof ParamVerify) {
                    verify((ParamVerify) parameterAnnotations[i][j], paramValues[i]);
                }
                break;
            }
        }
    }

    /**
     * 方法级别校验注解
     *
     * @param methodAnnotations
     * @param paramMap
     */
    private void methodAnnotationVerify(Annotation[] methodAnnotations, Map<String, Object> paramMap) {
        for (Annotation methodAnnotation : methodAnnotations) {
            if (methodAnnotation instanceof ParamObjVerifys) {// 是否是方法校验类型注解,排除其他类型注解
                // 获取ParamObjVerify注解集合
                ParamObjVerify[] ParamObjVerifyList = ((ParamObjVerifys) methodAnnotation).value();

                // ParamObjVerify遍历校验
                for (ParamObjVerify paramObjVerify : ParamObjVerifyList) {
                    // 获取配置校验的参数名,需要校验的属性名
                    String verifyKey = paramObjVerify.verifyKey();
                    if (!verifyKey.contains(".")) {
                        continue;
                    }
                    String paramName = verifyKey.split("\\.")[0];
                    String fieldName = verifyKey.split("\\.")[1];
                    Object paramValue = paramMap.get(paramName);

                    try {
                        Field field = paramValue.getClass().getDeclaredField(fieldName);
                        // 暴力破解,保证对private的属性的访问
                        field.setAccessible(true);
                        verify(paramObjVerify.paramRule(), field.get(paramValue));
                    } catch (NoSuchFieldException e) {
                        new ConfigServerException(ConfigServerExceptionFactor.paramMsg.CONFIG_ERROR);
                    } catch (IllegalAccessException e) {
                        new ConfigServerException(ConfigServerExceptionFactor.INTERNAL_ERROR);
                    }
                }
            }
        }
    }

    private void verify(ParamVerify paramVerify, Object value) {
        if (paramVerify == null) {
            throw new ConfigServerException(ConfigServerExceptionFactor.CONFIG_PARAM_TYPE_MISMATCH);
        }
        String paramValue = String.valueOf(value);

        if (paramVerify.isNotNull()) {
            if (value == null) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.IS_NULL);
            }
        }
        if (paramVerify.isNotBlank()) {
            if (StringUtils.isBlank(paramValue)) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.IS_NULL);
            }
        }
        if (paramVerify.maxLen() != Integer.MAX_VALUE) {
            if (paramValue.length() > paramVerify.maxLen()) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.OVER_MAX_LEN);
            }
        }
        if (paramVerify.minLen() != -1) {
            if (paramValue.length() < paramVerify.minLen()) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.OVER_MIN_LEN);
            }
        }
        if (paramVerify.isIp()) {
            if (!RegexUtil.isIp(paramValue)) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.INVALID_IP);
            }
        }
        if (paramVerify.isIps()) {
            if (!RegexUtil.isIps(paramValue)) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.INVALID_IP);
            }
        }
        if (paramVerify.isNumber()) {
            if (!RegexUtil.isNumber(paramValue)) {
                throw new ConfigServerException(ConfigServerExceptionFactor.paramMsg.IS_NULL);
            }
        }
    }
}

主要关注下注解值的获取方式,反射私有变量的暴力破解

总结

使用aop的切点定义很简单(需要扩展思考下aop的实现原理 动态代理的前置方法、后置方法。反射的应用)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值