在进行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的around
。resolveMethod
等函数是我自定义的处理函数,文章最后会贴完整的代码。
这个地方需要注意的是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));
}
}