2

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?

Oleg
  • 423
  • 1
  • 6
  • 9

2 Answers2

4

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:

    final String methodName = pjp.getSignature().getName();
    final MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
    Method method = methodSignature.getMethod();
    if (method.getDeclaringClass().isInterface()) {
        method = pjp.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes());    
    }

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:

public Object invoke(final MethodInvocation invocation) throws Throwable {}

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

Oleg
  • 423
  • 1
  • 6
  • 9
2
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();

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 only METHOD for example and see how it behaves)

Community
  • 1
  • 1
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • Okay, first of all, thank you very much for the response. Secondly, I feel bad about not expecting the result of getSignature() well enough to notice I could retrieve a Method object from there. It does indeed return it, thanks for the hint. But actually, the reason I want this object is to retrieve this method's annotations, and guess what? They are erased. The intercepted method does have annos. If I do it the "wrong" way of pjp.getTarget().getClass().getMethod() then annos will be returned. All annotations annotated as @Retention(RetentionPolicy.RUNTIME). Any advice? – Oleg Mar 07 '11 at 20:57
  • To clarify: final MethodSignature methodSignature = (MethodSignature)pjp.getSignature(); final Method method = methodSignature.getMethod(); final Annotation[] annos = method.getAnnotations(); "annos" variable will be null if I get the Method object this way. I marked your answer as accepted since it was the goal but unfortunately such object is of no help if annotations are erased. I also tried this: @Around("execution(@foo.bar.annotation.MyAnnotation * *.*(..))") did not help. – Oleg Mar 07 '11 at 21:01
  • @Oleg see http://stackoverflow.com/questions/2559255/spring-aop-how-to-get-the-annotations-of-the-adviced-method – Bozho Mar 07 '11 at 21:09
  • @Bozho Thanks, I saw that post when I searched for the null problem. Tried everything from that post. I still get null or weird exception (when binding annotation). And my annos are annotated as RetentionPolicy.RUNTIME. That does not help unfortunately. – Oleg Mar 07 '11 at 21:40
  • @Oleg - that's odd. Try cleaning your environment. – Bozho Mar 07 '11 at 21:47
  • Nope, cleaning did not help. I am away from my project sources right now, but when I get back to them I will post back), I think I found something, need to try first. – Oleg Mar 08 '11 at 08:49
  • @Oleg - fine. please share the result, I'm curious :) – Bozho Mar 08 '11 at 08:54