spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.68k stars 38.15k forks source link

Discover annotations on interface methods for AspectJ annotation pointcuts #22311

Open cdfive opened 5 years ago

cdfive commented 5 years ago

Affects: spring-aop:5.0.9.RELEASE, spring-boot-starter-aop:2.0.5.RELEASE

public interface FooService {

  @MyAnnotation 
  void hello();
}

public class FooServiceImpl implements FooService {

  void hello();
}
public class MyAspect {

  @Pointcut("@annotation(MyAnnotation")
     public void myPointcut() {
   }

  @Around("myPointcut()")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
     ...
  }
}
@Configuration
public class MyAopConfiguration {
    @Bean
    public MyAspect myAspect () {
        return new MyAspect();
    }
}

Using spring-aop like the code above, it doesn't work, around(ProceedingJoinPoint pjp) never executes.

If put the @MyAnnotation on the implemented method of FooServiceImpl, it works.

public interface FooService { 

  void hello();
}

public class FooServiceImpl  implements FooService {

  @MyAnnotation
  void hello();
}

Learned from two questions in Stack Overflow:

According to the Java 5 specification, non-type annotations are not inherited, and annotations on types are only inherited if they have the @Inherited meta-annotation.

It's seems that it's impossible in spring-aop.

I tried the code without spring-aop, only using aspectjweaver 1.7.4, and adding an aop.xml in resources/META-INF:

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
    <aspects>
        <aspect name="com.xxx.MyAspect"/>
    </aspects>
    <weaver options="-verbose -showWeaveInfo -Xset:weaveJavaxPackages=true" />
</aspectj>

and adding JVM parameter:

-javaagent:xxx/aspectjweaver-1.7.4.jar

It works no matter the @MyAnnotation is on the interface or class. And then in around(ProceedingJoinPoint pjp), I can get the @MyAnnotation with the API of pjp, like this:

MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Class<?> targetClass = pjp.getTarget().getClass();
Method method = targetClass.getDeclaredMethod(signature.getName(), signature.getMethod().getParameterTypes());
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);

or

Class<?>[] interfaces = targetClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
   Method method = targetClass.getDeclaredMethod(signature.getName(), 
   signature.getMethod().getParameterTypes());
   MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
}

So I think spring-aop may also have way to solve this problem, since it's a common use case for users to add annotation on the method of interface.

Something in AopUtils#getMostSpecificMethod, AspectJExpressionPointcut#getShadowMatch(Method targetMethod, Method originalMethod) may be about, I'm not sure.

Could you please give some help?

jhoeller commented 10 months ago

I'm afraid there is not much we can do about this on Spring's side since those annotation checks are performed by AspectJ itself which focuses on the concrete class to be weaved and generally follows Java language semantics where interface-declared annotations are not inherited.

jhoeller commented 10 months ago

Reopening again after re-reading the details and noticing that the AspectJ compiler does seem to behave differently there. We should investigate how it does that, maybe we can imitate that via custom ShadowMatch checks indeed.

constantinLu commented 7 months ago

Any advancement on this ?

FiruzzZ commented 4 months ago

it would be amazing to be able to listen to annotated methods from interfaces'

NicoStrecker commented 3 months ago

Also in need

ReXtrem commented 1 week ago

In need aswell!

@Around("@annotation(myAnnotation)")
public Object aroundMyAnnotation(
      ProceedingJoinPoint proceedingJoinPoint, myAnnotation annotation) throws Throwable {
      ...
}