Results 1 to 3 of 3

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

  1. #1
    Join Date
    Dec 2012
    Posts
    3

    Default 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. #2
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,629

    Default

    Post your pointcuts... There should be nothing preventing you writing a single method, the transaction support (for one example) does the same.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  3. #3
    Join Date
    Dec 2012
    Posts
    3

    Default

    Quote 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 at 09:05 AM. Reason: Replaced @within with @target

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •