Announcement Announcement Module
Collapse
No announcement yet.
Single @Around advice to handle both type and method level annotations? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Single @Around advice to handle both type and method level annotations?

    I want to apply @Around advice to certain methods (from the specific package) that are either:
    1. annotated with a custom annotation (@Retryable)
    2. defined in a class that is annotated with a custom annotation (@Retryable)

    Method level annotations can override type level annotation (this is similar to @Transactional):

    Code:
    @Retryable(maxRetries = 3)
    public class AdvisedClass {
        @Retryable(maxRetries = 1)
        public void advisedMethod1() {
            ...
        }
    
        // maxRetries = 3 should apply
        public void advisedMethod2() {
            ...
        }
        ...
    }
    I'm able to capture both conditions using two separate pointcut expressions (using @annotation and @target respectively) and my advice method is executed correctly:

    Code:
        @Around(...)
        public Object doDataAccessOperation(ProceedingJoinPoint jp, Retryable retryable) throws Throwable {
            int maxRetries = retryable.maxRetries();
            ...
        }
    However, when I combine both pointcut expressions, annotation variable that is passed to the advice method is null for one scenario. I'm wondering if the proper design is to have two separate @Around advice methods - one that captures type level and the other that captures method level annotations, i.e:

    Code:
        @Around(...)
        public Object doTypeLevel(ProceedingJoinPoint jp, Retryable retryable) throws Throwable {
            int maxRetries = retryable.maxRetries();
            ...
        }
    
        @Around(...)
        public Object doMetodLevelLevel(ProceedingJoinPoint jp, Retryable retryable) throws Throwable {
            int maxRetries = retryable.maxRetries();
            ...
        }
    Thanks,
    Lukasz

  • #2
    Post your pointcuts... There should be nothing preventing you writing a single method, the transaction support (for one example) does the same.

    Comment


    • #3
      Originally posted by Marten Deinum View Post
      Post your pointcuts... There should be nothing preventing you writing a single method, the transaction support (for one example) does the same.
      OK, here are more details.

      Main.java:

      Code:
      package com.mypackage;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      import org.springframework.stereotype.Component;
      
      import com.mypackage.service.MyService;
      
      @Component
      public class Main {
          @Autowired
          private MyService service;
      
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext(
                  "applicationContext.xml");
                  
              Main instance = context.getBean(Main.class);
      
              instance.go();
          }
          
          private void go() {
              service.operation1();
              service.operation2();
          }
      }
      MyService.java:

      Code:
      package com.mypackage.service;
      
      public interface MyService {
          void operation1();
          void operation2();
      }
      MyServiceImpl.java:

      Code:
      package com.mypackage.service;
      
      import org.springframework.stereotype.Service;
      
      import com.mypackage.Retryable;
      
      @Service
      @Retryable(maxRetries = 1)
      public class ServiceImpl implements MyService {
          @Retryable(maxRetries = 2)
          public void operation1() {
              System.out.println("operation1()");
          }
      
          // maxRetries = 1 should apply here
          public void operation2() {
              System.out.println("operation2()");
          }
      }
      Retryable.java:

      Code:
      package com.mypackage;
      
      import java.lang.annotation.Documented;
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Inherited;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Target({ElementType.TYPE, ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @Inherited
      @Documented
      public @interface Retryable {
          int maxRetries();
      }
      MyAdvice.java:

      Code:
      package com.mypackage;
      
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      @Component
      @Aspect
      public class MyAdvice {
          @Pointcut("execution(* com.mypackage.service.*.*(..))")
          public void serviceOperation() {
          }
          
          @Pointcut("serviceOperation() && @target(retryable)")
          public void operWithTypeLevel(Retryable retryable) {
          }
      
          @Pointcut("serviceOperation() && @annotation(retryable)")
          public void operWithMethodLevel(Retryable retryable) {
          }
          
          @Pointcut("operWithTypeLevel(retryable) || operWithMethodLevel(retryable)")
          public void operWithTypeOrMethodLevel(Retryable retryable) {
          }
      
          @Around("operWithTypeOrMethodLevel(retryable)")
          public Object advise(ProceedingJoinPoint jp, Retryable retryable)
              throws Throwable {
      
              System.out.println(String.format("jp: %s, r: %s", jp, retryable));
      
              return jp.proceed();
          }
      }
      applicationContext.xml:

      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      
      <beans
      	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      	xmlns="http://www.springframework.org/schema/beans"
      	xmlns:aop="http://www.springframework.org/schema/aop"
      	xmlns:context="http://www.springframework.org/schema/context"
      	xsi:schemaLocation="
      		http://www.springframework.org/schema/aop
      		http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
      		http://www.springframework.org/schema/beans
      		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      		http://www.springframework.org/schema/context
      		http://www.springframework.org/schema/context/spring-context-3.0.xsd"
      >
      	<context:component-scan base-package="com.mypackage"/>
      	<aop:aspectj-autoproxy/>
      </beans>
      Execution output:

      Code:
      jp: execution(void com.mypackage.service.MyService.operation1()), r: @com.mypackage.Retryable(maxRetries=2)
      operation1()
      jp: execution(void com.mypackage.service.MyService.operation2()), r: null
      operation2()
      As you can see, the passed-in annotation is null for the second method, where I was expecting the type level annotation to apply. If I refactor the above code to use two advice methods (one to handle each case), it works as expected. I also came across another post here (I don't have a link now), where someone mentioned that having a single pointcut would be ambiguous.

      Thanks,
      Lukasz
      Last edited by lukasz74nj; Jan 4th, 2013, 09:05 AM. Reason: Replaced @within with @target

      Comment

      Working...
      X