Eugene Rozov opened SPR-7664 and commented

According to reference (paragraph 10.5.6), annotation may be placed on an interface (or an interface method) and this should work correctly if interface-based proxies are used.

But seems that Spring AOP failed to handle situation when annotation is placed on interface method, that's why pointcuts like execution(@com.xyz.Secured) are not working.

Sample project is attached.


Affects: 2.5.6, 3.0.4

Reference URL: http://forum.springsource.org/showthread.php?t=67388

Attachments: - test-aspect.zip (6.30 kB)

Referenced from: commits https://github.com/spring-projects/spring-framework-issues/commit/65ac173b29825aa460bda47c5b751515138b5a4c

Comment From: spring-projects-issues

Stevo Slavić commented

Spring Security for @org.springframework.security.access.annotation.Secured and Spring Framework for @org.springframework.transaction.annotation.Transactional are not using AspectJ (only) to find matching joinpoints, but instead they are using custom logic (when configured) to enable security and transactional aspects on beans that implement interfaces whose methods are annotated with mentioned annotations.

With custom aspects and AspectJ pointcuts in Spring AOP one cannot achieve the same behaviour because Spring AOP will use AspectJ only to evaluate whether given bean/class method implementation matches a pointcut expression and, to quote Spring reference docs, "AspectJ follows Java's rule that annotations on interfaces are not inherited". Actually all non-type annotations, so method/field/constructor/package annotations, are not inherited (see this and javadoc of @java.lang.annotation.Inherited for more info). On some reasons why interface method annotations are not inherited see here. So, for pointcut targeting execution of class method annotated with given annotation AspectJ will return that method does not match pointcut expression if the annotation is not on the method implementation being executed - e.g. if it's only on method declaration in interface like in your example. Even if abstract class had method annotated, and concrete class did overide that method without annotation - that method would not match expression.

Spring AOP namespace <aop:config> tag enables schema style AOP and is processed by org.springframework.aop.config.ConfigBeanDefinitionParser Interesting code is in - org.springframework.aop.aspectj.AspectJExpressionPointcut - public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) --- private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod)

Spring TX namespace tx:annotation-driven tag enables declarative transaction demarcation via @Transactional annotation and is processed by org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser Interesting code is in - org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource - private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass)

Spring Security namespace <global-method-security> tag enables declarative security via @Secured annotation and is processed by org.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParser Interesting code is in - org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource - org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource - protected Collection\ findAttributes(Method method, Class<?> targetClass) - org.springframework.core.annotation.AnnotationUtils - public static \ A findAnnotation(Method method, Class\ annotationType)

So framework enables this different behaviour for it's own annotations/aspects. For user/custom aspects it relies on what AspectJ/Java support, but it doesn't prevent you from adding custom logic where/if needed.

AFAICT, although bit annoying, this works as designed.

Comment From: spring-projects-issues

Chris Beams commented

Stevo's assessment is correct. Eugene, in your original description you wrote

According to reference (paragraph 10.5.6), annotation may be placed on an interface (or an interface method) and this should work correctly if interface-based proxies are used.

This statement is in fact accurate in the context of the @Transactional annotation (section 10 covers transaction management), but for the reasons described above, does not apply generally to custom aspects. Unfortunately this is a Java limitation that cascades down to AspectJ. You can get around this with some effort as we have done in dedicated cases like @Transactional and @Secured, but the easiest route is to simply annotated the concrete type you're dealing with.

Comment From: puppylpg

Stevo Slavić commented

Spring Security for @org.springframework.security.access.annotation.Secured and Spring Framework for @org.springframework.transaction.annotation.Transactional are not using AspectJ (only) to find matching joinpoints, but instead they are using custom logic (when configured) to enable security and transactional aspects on beans that implement interfaces whose methods are annotated with mentioned annotations.

With custom aspects and AspectJ pointcuts in Spring AOP one cannot achieve the same behaviour because Spring AOP will use AspectJ only to evaluate whether given bean/class method implementation matches a pointcut expression and, to quote Spring reference docs, "AspectJ follows Java's rule that annotations on interfaces are not inherited". Actually all non-type annotations, so method/field/constructor/package annotations, are not inherited (see this and javadoc of @java.lang.annotation.Inherited for more info). On some reasons why interface method annotations are not inherited see here. So, for pointcut targeting execution of class method annotated with given annotation AspectJ will return that method does not match pointcut expression if the annotation is not on the method implementation being executed - e.g. if it's only on method declaration in interface like in your example. Even if abstract class had method annotated, and concrete class did overide that method without annotation - that method would not match expression.

Spring AOP namespace <aop:config> tag enables schema style AOP and is processed by org.springframework.aop.config.ConfigBeanDefinitionParser Interesting code is in

  • org.springframework.aop.aspectj.AspectJExpressionPointcut

  • public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) --- private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod)

Spring TX namespace tx:annotation-driven tag enables declarative transaction demarcation via @Transactional annotation and is processed by org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser Interesting code is in

  • org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource

  • private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass)

Spring Security namespace <global-method-security> tag enables declarative security via @Secured annotation and is processed by org.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParser Interesting code is in

  • org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource
  • org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource

  • protected Collection findAttributes(Method method, Class<?> targetClass)

  • org.springframework.core.annotation.AnnotationUtils

  • public static A findAnnotation(Method method, Class annotationType)

So framework enables this different behaviour for it's own annotations/aspects. For user/custom aspects it relies on what AspectJ/Java support, but it doesn't prevent you from adding custom logic where/if needed.

AFAICT, although bit annoying, this works as designed.

Saved my day! Thanks a lot!