全局校验注解实现,需要思考的问题
- 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的实现原理 动态代理的前置方法、后置方法。反射的应用)