在進行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));
}
}