Getting a Template/Generic java.lang.reflect.Metho

2019-05-29 02:58发布

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?

2条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-05-29 03:49

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

查看更多
聊天终结者
3楼-- · 2019-05-29 03:53
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)

查看更多
登录 后发表回答