This question would not have existed if AspectJ worked the same way as EJB interceptors work.
Consider basic scenario the EJB-interceptor way:
@AroundInvoke
public Object log(final InvocationContext ctx) throws Exception {
// do stuff before
final Object result = ctx.proceed();
// do stuff after
return result;
}
Now I need the Method object that's being intercepted. Here I can simply do this:
Method method = ctx.getMethod();
And that's it, after this I will be inspecting intercepted method's annotations.
Now I'm working with application which is deployed to servlet container (Tomcat) and hence has no EJBs. AOP in it is implemented using Spring + AspectJ.
The around method looks like this:
@Around("execution(* foo.bar.domain.integration.database.dao..*(..))")
public Object log(final ProceedingJoinPoint pjp) throws Throwable {
// do stuff before
final Object result = pjp.proceed();
// do stuff after
return result;
}
Here I can no longer do this:
Method method = pjp.getMethod(); // no such method exists
Instead I am forced to get the Method object myself using reflection as follows:
final String methodName = pjp.getSignature().getName();
final Object[] arguments = pjp.getArgs();
final Class[] argumentTypes = getArgumentTypes(arguments);
final Method method = pjp.getTarget().getClass().getMethod(methodName, argumentTypes);
It works until you want to get a hold of the template method:
@Transactional
public <T extends Identifiable> T save(T t) {
if (null == t.getId()) {
create(t);
return t;
}
return update(t);
}
Assume you invoke this method as follows:
Person person = new Person("Oleg");
personService.save(person);
You will receive a:
Caused by: java.lang.NoSuchMethodException: foo.bar.domain.integration.database.dao.EntityService.save(foo.bar.domain.entity.Person)
which is thrown by:
pjp.getTarget().getClass().getMethod()
The problem is that generics do not exist at runtime and the actual method signature is:
public Identifiable save(Identifiable t) {}
final Class[] argumentTypes will contain one element and its type will be Person. So this call:
pjp.getTarget().getClass().getMethod(methodName, argumentTypes);
will be looking up method:
save(Person p)
and will not find it.
So the question is: how do I get instance of java's template Method object which represents the exact method that has been intercepted?
I guess one of the ways is to do it the brute force way: get arguments superclasses/interfaces and try getting the method using those types until you no longer get the NoSuchMethodException but that is plain ugly. Is there a normal clean way I can do that?
Okay I have now my problem solved. After a closer look at what: methodSignature.getMethod() returns I noticed it returned the interface instead of the implementing class, and of course there were no annotations on the interface. This is different from EJB interceptors where getMethod() returns the implementing class method.
So the final solution is this:
and if you like, you can handle interface annotations here as well, if needed.
Also notice this bit: method.getParameterTypes() without this it would still throw NoSuchMethodException so it's a good thing we could get the correct signature via ((MethodSignature)pjp.getSignature()).getMethod();
Hopefully, no more surprises, even though I am not happy about using reflection here, I would just prefer to have the method instance of the implementing class, as in InvocationContext of EJB.
BTW, Spring's native approach without AspectJ:
returns interface and not implementing class as well. Checked it for the sake of knowledge too.
Best Regards and thanks for helping, I really appreciate it. Oleg
This is not immediately obvious, for which the API is to blame.
Also see Spring AOP: how to get the annotations of the adviced method. I haven't tested this myself. The OP there says it solved the issue for him. For another use an additional
@Around(annotation...)
annotation was needed. (Try setting the target to be onlyMETHOD
for example and see how it behaves)