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\
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.Securedand Spring Framework for @org.springframework.transaction.annotation.Transactionalare 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.Inheritedfor 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 byorg.springframework.aop.config.ConfigBeanDefinitionParserInteresting 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-driventag enables declarative transaction demarcation via@Transactionalannotation and is processed byorg.springframework.transaction.config.AnnotationDrivenBeanDefinitionParserInteresting 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@Securedannotation and is processed byorg.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParserInteresting 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!