Announcement Announcement Module
Collapse
No announcement yet.
Unable to get MethodInterceptor (AOP Alliance) to match based on method annotation Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Unable to get MethodInterceptor (AOP Alliance) to match based on method annotation

    I'm trying to do method annotation based interception using AOP alliance's MethodInterceptor. An AOP-alliance approach is a core requirement, so going a purely AspectJ route isn't an option.

    Here's my setup:

    Interface & Classes
    Code:
    public class MyAdvisor implements MethodInterceptor {
        // implementation reads annotation and executes
        ...
    }
    
    public class ServiceImpl implements Service {
        @Override
        public SomeObject nameOfAnnotatedMethod(String args) throws CustomException {
            ...
        }
    }
    
    public interface Service {
        @MyAnnotation(...)
        public SomeObject nameOfAnnotatedMethod(String args) throws CustomException;
    }
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
       ...
    }
    Spring Config:
    Code:
        <aop:config>
            <aop:pointcut id="myPointcut"
                          expression="execution(* nameOfAnnotatedMethod(..))"/>
          
            <aop:advisor id="advisor" advice-ref="myAdvisor" 
                         pointcut-ref="myPointcut" />
        </aop:config>
        
        <bean id="myAdvisor" class="package.MyAdvisor">
            ...
        </bean>
    First off the expression "execution(* nameOfAnnotatedMethod(..))" works. nameOfAnnotatedMethod(...) is intercepted and MyAdvisor can see the annotation information. So far, so good. But my goal is to NOT specify method name(s) and instead just intercept any method annotated with @MyAnnotation (that's the beauty of annotations right?).

    However, I can't seem to come up with any pointcut expression which does this. I've tried the following ones which all compile at runtime (ie: Spring doesn't complain that they are invalid), but the interception never occurs with any:
    • "execution(@package.MyAnnotation * nameOfAnnotatedMethod(..))"
    • "execution(@package.MyAnnotation * *(..))"
    • "execution(@package.MyAnnotation * *.*(..))"

    What expression can I use to do this? Or is there some fundamental AOP-alliance limitation that prevents this from being possible (which would be confusing since Spring just uses AspectJ to evaluate the expression regardless of the advice engine, right?)?

    EDIT:

    I forgot to mention dependencies:
    • spring-aop:3.1.2.RELEASE (using 3.1.2.RELEASE across the board for spring)
    • cglib:2.2.2
    • aspectjweaver:1.7.0
    • aopalliance:1.0
    Last edited by Temujin_12; Jan 11th, 2013, 04:42 PM.

  • #2
    Okay, I solved it. My solution stemmed from section 8.8.2 from the Spring docs:

    The aspect that interprets @Transactional annotations is the AnnotationTransactionAspect. When using this aspect, you must annotate the implementation class (and/or methods within that class), not the interface (if any) that the class implements. AspectJ follows Java's rule that annotations on interfaces are not inherited.
    This seemed a bit weird to me since, when I use the method-name based pointcut expression that worked above, my MethodInterceptor's invoke(...) method was able to reflectively retrieve the annotation from the interface (seems like it's inherited to me). Is this "no annotation inheritance" rule based on an old version of the Java spec (I'm using Java 1.7)?

    Anyways, I copied the annotations onto the ServiceImpl and ran my tests. It worked!

    I then removed the annotations from the Service interface (it's bad to have them in two places) and my tests failed. Hmmm...

    Some more reading and I realized that I needed to proxy the target class since the default <aop:config> setting is to use standard interface-based proxies. I added "proxy-target-class="true"" attribute inside the <aop:config ... > tag and now it works!

    It's kind of a catch-22. Implementation annotations are not inherited and the class-level annotations are lost in interface-based proxying. It looks like in order to do this kind of annotation-based pointcut expression, you MUST use the cglib class-based proxying.

    So, my final setup is:

    Interface & Classes
    Code:
    // no change here
    public class MyAdvisor implements MethodInterceptor {
        // implementation reads annotation and executes
        ...
    }
    
    public class ServiceImpl implements Service {
        // annotation moved to this implementation
        @Override
        @MyAnnotation(...)
        public SomeObject nameOfAnnotatedMethod(String args) throws CustomException {
            ...
        }
    }
    
    public interface Service {
        // no more annotation here
        public SomeObject nameOfAnnotatedMethod(String args) throws CustomException;
    }
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
       ...
    }
    Spring Config:
    Code:
        <!-- added proxy-target-class="true" for cglib class-based proxying -->
        <!-- also simplified the pointcut expression to just look for the annotation -->
        <aop:config proxy-target-class="true">
            <aop:pointcut id="myPointcut"
                          expression="@annotation(package.MyAnnotation)"/>
          
            <aop:advisor id="advisor" advice-ref="myAdvisor" 
                         pointcut-ref="myPointcut" />
        </aop:config>
        
        <bean id="myAdvisor" class="package.MyAdvisor">
            ...
        </bean>
    I hope this helps someone.

    And if my assumption is false here (that you MUST use cglib class-based proxying to use a pointcut expression that looks for method annotations) then someone please let me know.

    Comment

    Working...
    X