基于方法参数注解切面的实现方式参考
https://stackoverflow.com/questions/2766844/pointcut-matching-methods-with-annotated-parameters
背景
实现文件管理系统的时候对路径的修正采用了切面的方式:
RevisePath
1 2 3 4
| @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface RevisePath { }
|
RevisePathAspect
切点的声明
1 2 3
| @Pointcut("execution(public * *(.., @com.forg.kfile.component.aspect.RevisePath (*), ..))") public void reviseParamPathPointcut() { }
|
修正路径参数需要改变方法的参数
1 2 3 4
| @Around("reviseParamPathPointcut()") public Object revisePath(ProceedingJoinPoint joinPoint) throws Throwable { ...... }
|
注解参数的识别与修改
Around注解方法的第一个参数类型是 ProceedingJoinPoint ,注意这个类是所属的包是org.aspectj.lang , spring 实现的类是 MethodInvocationProceedingJoinPoint。
获取切点的参数是有直接的方法的
1 2
| java.lang.Object[] getArgs() Returns the arguments at this join point.
|
但是哪一个参数是有注解的参数并不能直接获取到。起初我的实现比较复杂:
通过解析方法签名参数类型,通过反射找到对应对应的方法。在从方法上找到参数对应的所有的注解判断参数位置,确定参数位置后修改参数内容,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| @Around("reviseParamPathPointcut()") public Object revisePath(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if(args.length <= 0){ return joinPoint.proceed(); }
if(args.length == 1 ){
if( args[0] instanceof String || args[0] == null){ String path = (String)args[0];
args[0] = revisePath(path); return joinPoint.proceed(args); } }
Object target = joinPoint.getTarget();
String longString = joinPoint.getSignature().toLongString(); String [] paramClass = longString.substring( longString.lastIndexOf("(")+1, longString.lastIndexOf(")")).split(",");
Class [] clz = new Class[args.length];
if(paramClass.length != args.length){ return joinPoint.proceed(args); }
for(int i = 0; i< args.length ;i++){ if(args[i] ==null){ Class c = Class.forName(paramClass[i]); clz[i] = c; continue; } clz[i] = args[i].getClass(); }
try { Method method = target.getClass().getDeclaredMethod(joinPoint.getSignature().getName(),clz);
Annotation[][] annotations = method.getParameterAnnotations();
if(annotations == null || annotations.length == 0){ return joinPoint.proceed(args); }
List<Integer> pos = new ArrayList<>(args.length); int p = 0; for(Annotation[] as : annotations){ for(Annotation a : as){ if(a instanceof RevisePath){ pos.add(p); break; } } p++; }
if(pos.size() <= 0){ return joinPoint.proceed(args); }
for (Integer _p : pos){
if(!(clz[_p].equals(String.class) )){ continue; }
if(StringUtils.isEmpty(args[_p])){ args[_p] =revisePath((String)args[_p]); } }
} catch (NoSuchMethodException e) { logger.error("error",e); return joinPoint.proceed(args); }
Object result = joinPoint.proceed(args); return result; }
|
其中确定调用方法就很麻烦,反射通过方法名称与参数类型去查找。因为我是通过spring做的aop,所以ProceedingJoinPoint的类型应该是MethodInvocationProceedingJoinPoint,而MethodInvocationProceedingJoinPoint类的说明很清楚的指名:
1
| An implementation of the AspectJ ProceedingJoinPoint interface wrapping an AOP Alliance MethodInvocation.
|
它是MethodInvocation的包装。拿到MethodInvocation就能拿到method。而MethodInvocationProceedingJoinPoint中的methodInvocation并没有暴露出来,但是可以通过其中的MethodSignature获取
1 2 3 4 5 6
| MethodInvocationProceedingJoinPoint methodInvocationProceedingJoinPoint = (MethodInvocationProceedingJoinPoint)joinPoint;
Method method = ((MethodSignature)methodInvocationProceedingJoinPoint.getSignature()).getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
|
当然也可以直接通过反射获取
1 2 3 4 5 6
| MethodInvocationProceedingJoinPoint methodJoinPoint = (MethodInvocationProceedingJoinPoint) pjp; Field methodInvocationfield = methodJoinPoint.getClass().getDeclaredField("methodInvocation");
methodInvocationfield.setAccessible(true);
Annotation[][] argAnnotations = ((ReflectiveMethodInvocation) methodInvocationfield.get(methodJoinPoint)).getMethod().getParameterAnnotations();
|
后面就是简单的定位到参数然后修改值后在传入proceed方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| List<Integer> positionList = new ArrayList<>(1); int p = 0; for(Annotation[] as : annotations){ for(Annotation a : as){ if(a instanceof RevisePath){ positionList.add(p); break; } } p++; }
if(positionList.size() <= 0){ return joinPoint.proceed(args); }
for (Integer position : positionList){ if(args[position] == null || args[position] instanceof String){ args[position] = revisePath((String)args[position]); } } return joinPoint.proceed(args);
|
其他需要知道的
刚开始我使用的是@Before来修改参数,发现@Before是不能修改参数内容的,原因是
MethodInvocationProceedingJoinPoint
1 2 3 4 5 6
| public Object[] getArgs() { if (this.args == null) { this.args = this.methodInvocation.getArguments().clone(); } return this.args; }
|
参考
MethodInvocationProceedingJoinPoint
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.html
MethodInvocationProceedingJoinPoint example
https://www.programcreek.com/java-api-examples/?api=org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint
Pointcut matching methods with annotated parameters
https://stackoverflow.com/questions/2766844/pointcut-matching-methods-with-annotated-parameters
AspectJ: Pointcut to declare and retrieve an annotation of a method’s parameter
https://stackoverflow.com/questions/63894176/aspectj-pointcut-to-declare-and-retrieve-an-annotation-of-a-methods-parameter