Reputation: 8837
I am weaving a method with AspectJ and having a Around
advice applied to it. In the advice logic I then want to access all parameters of that method that are annotated. I do this so I can filter on the specific annotation I am looking for.
The problem is that after I call getAnnotatedParameterTypes()
of java.lang.reflect
I receive an array of AnnotatedType
. I can find that expected parameter I was looking for in there. However when I want to access the annotation type of that parameter - because I want to filter by its type - there is no annotation present.
I expected it to be present - well since it says it's an AnnotatedType
- so where is the annotation :D
Here is the code to look through
@Around("@annotation(com.mystuff.client.annotation.Query)")
public void doStuff(ProceedingJoinPoint joinPoint) {
Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();
Optional<Method> first = Arrays.stream(methods).findFirst();
if (first.isPresent()) {
Method method = first.get();
AnnotatedType[] annotatedParameterTypes = method.getAnnotatedParameterTypes();
AnnotatedType annotatedParameterType = annotatedParameterTypes[0];
LOG.info(Arrays.toString(annotatedParameterType.getAnnotations()));
}
}
Log Output
2020-10-10 22:17:11.821 INFO 215068 --- [ Test worker] com.mystuff.Aspect : []
My Annotations
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query{
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Response {
}
The class where the whole magic is tested on
@Component
class TestCandidate {
@Query
public TestResponseModel useData(@Response TestResponseModel model){
return model;
}
}
Upvotes: 1
Views: 901
Reputation: 67297
Your aspect code has several problems:
Your target method returns something but the advice method's return type is void
, i.e. it implicitly will never match anything other than void
methods. It will definitely not match your sample useData(..)
method, though. So you need to make the return type Object
or TestResponseModel
if you want to limit the return type.
The @Around
advice never calls joinPoint.proceed()
, i.e. the target method will not be executed but skipped.
If you just want to log @Response
parameters and not modify any parameters or the result before/after proceeding, actually a simple @Before
advice would suffice. I am going to keep your around advice in my sample code, though, just in case you want to do something special with those parameters.
The first two lines in your advice method do the following:
This does not make much sense. Why would you always do something with the first method without regard to what method it is? You want to identify the parameter annotations on the target method being intercepted by the advice, don't you? Probably the first method's first parameter does not have any annotations, which is why none are being logged. You are actually lucky that the first method has a parameter at all, otherwise annotatedParameterTypes[0]
would yield an "array index out of bounds" exception.
Here is what you want to do instead. BTW, I am presenting a full MCVE here, as you should have done in the first place. I am using plain AspectJ, not Spring AOP, so I do not use any @Component
annotations. But if you are a Spring user, you can just make both the aspect and the target class Spring components/beans in order to make it work.
Annotations + dummy helper class:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface Query {}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Response {}
package de.scrum_master.app;
public class TestResponseModel {}
Target class with positive/negative test cases + driver application
package de.scrum_master.app;
class TestCandidate {
@Query
public TestResponseModel useData(@Response TestResponseModel model) {
return model;
}
@Query
public TestResponseModel dummyOne(TestResponseModel model) {
return model;
}
public TestResponseModel dummyTwo(@Response TestResponseModel model) {
return model;
}
@Query
public TestResponseModel multipleResponses(@Response TestResponseModel model, @Response String anotherResponse, int i) {
return model;
}
public static void main(String[] args) {
TestCandidate candidate = new TestCandidate();
TestResponseModel model = new TestResponseModel();
candidate.dummyOne(model);
candidate.dummyTwo(model);
candidate.useData(model);
candidate.multipleResponses(model, "foo", 11);
}
}
The expectation would be that the advice gets triggered for methods useData
and multipleResponses
and that the special case of multiple @Response
parameters in the latter method is also handled correctly by the aspect.
@Around
aspect variant:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.Response;
@Aspect
public class QueryResponseInterceptor {
@Around(
"@annotation(de.scrum_master.app.Query) && " +
"execution(* *(.., @de.scrum_master.app.Response (*), ..))"
)
public Object doStuff(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (int i = 0; i < args.length; i++) {
for (Annotation annotation : annotationMatrix[i]) {
if (annotation.annotationType().equals(Response.class)) {
System.out.println(" " + args[i]);
break;
}
}
}
return joinPoint.proceed();
}
}
Please note how the execution()
pointcut limits to methods with parameters carrying @Response
annotations, wherever in the parameter list they might occur.
@Before
aspect variant:
A simpler variant if you just want to log the annotated parameters would be this aspect with a @Before
advice and less boilerplate:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.Response;
@Aspect
public class QueryResponseInterceptor {
@Before(
"@annotation(de.scrum_master.app.Query) && " +
"execution(* *(.., @de.scrum_master.app.Response (*), ..))"
)
public void doStuff(JoinPoint joinPoint) {
System.out.println(joinPoint);
Object[] args = joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (int i = 0; i < args.length; i++) {
for (Annotation annotation : annotationMatrix[i]) {
if (annotation.annotationType().equals(Response.class)) {
System.out.println(" " + args[i]);
break;
}
}
}
}
}
See? Now you really can use a void
return type, do not need to call proceed()
and hence also not throw Throwable
.
Console log:
For both aspect variants the console log is the same.
execution(TestResponseModel de.scrum_master.app.TestCandidate.useData(TestResponseModel))
de.scrum_master.app.TestResponseModel@71318ec4
execution(TestResponseModel de.scrum_master.app.TestCandidate.multipleResponses(TestResponseModel, String, int))
de.scrum_master.app.TestResponseModel@71318ec4
foo
Upvotes: 1