AOP参数校验

场景

在面试中,可能会问道AOP的适用场景有哪些,参数校验就是其中的一个例子


在Java使用登录or注册功能时,会需要用户提交邮箱手机号等信息,此时前端可能会做参数校验,以下代码是基于后端的参数校验,可适用于大部分场景。


功能

代码结构

核心的代码在GlobalOperatcionAspect类中,通过反射获取到Controller中的参数值。

@Globallnterceptor:

全局注解,个人理解它可以管理其它的注解。

@Target({ElementType.METHOD})
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Mapping
public @interface Globallnterceptor {
    boolean checkParams() default false; //是否检查参数 默认不检查
}

@VerifyParam:

具体的参数校验注解,只有当@Globallnterceptor中的checkParams为true时才会初步生效。因为此注解中还有required为true时才是真正开启了参数校验。

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
public @interface VerifyParam {

    int min() default -1;
    //最大长度
    int max() default -1;
    //是否必填
    boolean required() default false;
    //正则表达式校验
    VerifyRegexEnum regex() default VerifyRegexEnum.NO;
}

GlobalOperatcionAspect:

在此类中:

  1. 获取到方法的参数、方法名等……
  2. 检查@Globallnterceptor注解中的checkParams是否为true
  3. 若为true,则进行参数校验,反之则不校验
  4. validateParams()方法中,检查@VerifyParam注解的required是否为true
  5. 判断参数的类型,若为基本类型,则进入checkValue()方法判断,反之进入checkObjValue()方法
@Aspect
@Component("globalOperatcionAspect")
public class GlobalOperatcionAspect {

    private static final Logger logger = LoggerFactory.getLogger(GlobalOperatcionAspect.class);
    private static final String[] TYPE_BASIS = {"java.lang.String","java.lang.Integer","java.lang.Long"
	,"java.lang.Double"};
//    private static final String TYPE_INTEGER = "java.lang.Integer";
//    private static final String TYPE_LONG = "java.lang.Long";

    @Pointcut("@annotation(com.easypan.annotation.Globallnterceptor)")
    private void requestInterceptor() {
    }

    @Before("requestInterceptor()")
    public void interceptorDo(JoinPoint point) throws BusinessException {
        try{
			//获取目标对象
            Object target = point.getTarget();
            //获取方法参数
            Object[] argements = point.getArgs();
            //获取方法名
            String methodName = point.getSignature().getName();
            //获取参数类型
            Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
			//反射获取该方法
            Method method = target.getClass().getMethod(methodName,parameterTypes);
			//获取 method 方法上的 Globallnterceptor 注解对象。如果该方法上没有该注解,
			//则返回 null。如果有注解,则可以通过注解对象来获取注解中定义的属性值
            Globallnterceptor interceptor = method.getAnnotation(Globallnterceptor.class);
            if (interceptor == null){
                return;
            }
            /**
             * 参数校验
             */
            if(interceptor.checkParams()){
                validateParams(method,argements);
            }
			//缺少一个全局异常
        }catch (BusinessException e){
			logger.error("参数校验失败",e);
			throw new BusinessException(ResponseCodeEnum.CODE_500);
		}catch (Throwable e){
			logger.error("参数校验失败",e);
			throw new BusinessException(ResponseCodeEnum.CODE_500);
		}
	}

	/**
	 * 用于判断校验的具体类型
	 * @param method
	 * @param argements
	 */
    private void validateParams(Method method, Object[] argements)  {
        Parameter[] parameters = method.getParameters();//参数多个,所以是数组
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];// 当前方法
            Object value = argements[i]; // 当前方法调用时传入的参数值
            VerifyParam verifyParam = parameter.getAnnotation(VerifyParam.class);
            if (verifyParam == null){
                continue;
            }
			//判断当前类型是否为基本类型
			//parameter.getParameterizedType().getTypeName())反射方法,用于获取方法参数的类型名称。
            if (ArrayUtils.contains(TYPE_BASIS,parameter.getParameterizedType().getTypeName())){
				//值
				checkValue(value,verifyParam);
            }else {
                //对象
				checkObjValue(parameter,value);
            }
        }
    }

	public void checkObjValue(Parameter parameter,Object value)  {
		try {
			String typename = parameter.getParameterizedType().getTypeName();
			//获取该类型的class对象
			Class calssz = Class.forName(typename);
			//获取该对象的所有字段
			Field[] fileds = calssz.getDeclaredFields();
			for (Field field : fileds){
				VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class);
				if (fieldVerifyParam == null){
					continue;
				}
				//如果字段有非public的值,需要调用此方法,否则获取不到
				field.setAccessible(true);
				//获取当前字段的值
				Object resultValue = field.get(value);
				checkValue(resultValue,fieldVerifyParam);
			}
		}catch (BusinessException  e){
			logger.error("参数校验失败",e);
			throw e;
		} catch (Exception e) {
			logger.error("参数校验失败",e);
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
	}

    // 获取VerifyParam里面定义的属性
    private void checkValue(Object value,VerifyParam verifyParam){
		Boolean isEmpty = value==null|| StringTools.isEmpty(value.toString());
		Integer length = value==null?0:value.toString().length();
		//校验空
		if (isEmpty && verifyParam.required()){
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		//校验长度
		if (!isEmpty && verifyParam.max() !=-1 && verifyParam.max()<length||verifyParam.min()!=-1&&verifyParam.min()>length){
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
		//校验正则
		if (!isEmpty && StringTools.isEmpty(verifyParam.regex().getRegex()) && VerifyUtils.verify(verifyParam.regex(),String.valueOf(value))){
			throw new BusinessException(ResponseCodeEnum.CODE_600);
		}
    }

}

StringTools:

public class StringTools {

    /**
     * 判断字符串为空
     * @param str
     * @return
     */
    public static boolean isEmpty(String str){
        if (str==null || "".equals(str) || "null".equals(str) || "\u0000".equals(str)) {
            return true;
        } else return "".equals(str.trim());
    }
}

VerifyUtils:

public class VerifyUtils {

    // regex为指定的匹配规则,value为传入的待匹配校验的值
    public static boolean verify(String regex,String value){
        if(StringTools.isEmpty(value)){
            return false;
        }
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(value);
        //指示整个value字符串是否与regex模式匹配。
        return matcher.matches();
    }

    public static boolean verify(VerifyRegexEnum regex, String value){
        return verify(regex.getRegex(),value);
    }
}

VerifyRegexEnum:

定义具体的匹配规则。

public enum VerifyRegexEnum {

    NO("", "不校验"),
    EMAIL("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", "邮箱"),
    PASSWORD("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$","密码");


    private String regex;
    private String desc;



    VerifyRegexEnum(String regex, String desc) {
        this.regex = regex;
        this.desc = desc;
    }

    public String getRegex() {
        return regex;
    }

    public void setRegex(String regex) {
        this.regex = regex;
    }

    public String getDesc() {
        return desc;
    }

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

具体方法的使用:

@RequestMapping("/sendEmailCode")
	@Globallnterceptor(checkParams = true)
    public ResponseVO sendEmailCode(HttpSession session,
									@VerifyParam(required = true,regex = VerifyRegexEnum.EMAIL) String email,
									@VerifyParam(required = true) String checkCode,
									@VerifyParam(required = true) Integer type) throws MessagingException {
		try {
			//如果前端没有传Email checkcode等,可能会报空指针异常,每个都是用if判断太麻烦,可以使用AOP参数校验
			if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))){
				throw new BusinessException("验证码错误");
			}
			emailInfoService.sendEmailCode(email,type);
			return getSuccessResponseVO(null);
		}finally {
			session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
		}
	}
消息盒子
# 您需要首次评论以获取消息 #
# 您需要首次评论以获取消息 #

只显示最新10条未读和已读信息