Sunday, 18 November 2012

Spring AOP Advice Ordering

Spring AOP Advice Ordering
Spring AOP Advice Ordering

If you are new to spring AOP. You can go through my previous blog:

Now before we create the sample code for Spring AOP advice ordering, lets see what Spring framework website says on advice ordering:

What happens when multiple pieces of advice all want to run at the same join point? 

Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).
When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.
When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.

Spring AOP Advice Ordering Example

In the example below we are trying to use all the 5 advices on the same joinpoint. 
We are going to use the same example that we used in our previous blog on Spring AOP.
We are taking a scenario where there is a small party and different advices are getting used to cater different situations:
  1. @Before advice - Will be used for authentication of guests. Only people in guest list can come. 
  2. @Around advice - Will be used for Audit. Guest's entry and exit timings should be noted. 
  3. @AfterThrowing - Will be used for Exception handling. If a guest gets too drunk then call a cab. 
  4. @AfterReturning - Will be used to Check Normal flow. Give good bye gift to guests who didn't get too drunk.
  5. @After - Will be used to handle both normal or Exception conditions. Send a thank you email to all the guest irrespective of whether they were drunk or not.
We want that the advices to be executed in the following order.
@Before advice
@Around advice
@AfterThrowing
@AfterReturning
@After


Folder structure of the project:


Spring AOP Advice Ordering
Spring AOP Advice Ordering

Lets start with creating a PartyService with letsParty() method to show the use of advices.
 package com.blogspot.javasampleprogram.service;  
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 public interface IPartyService {  
      public void letsParty(PartyPeople people) throws Exception;  
 }  
PartyService Implementation class
 package com.blogspot.javasampleprogram.service;  
 import com.blogspot.javasampleprogram.exception.GotTooDrunkException;  
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 public class PartyServiceImpl implements IPartyService {  
      /**  
       * guests wait for some time in party then leave.  
       * If a guest gets too drunk then they are taken out.  
       */  
      public void letsParty(PartyPeople people) throws Exception {  
                Thread.sleep(200);  
                // if people get drunk then get them out
                if (people.isDrunk()) {  
                     throw new GotTooDrunkException();  
                }  
      }  
 }  

Data transfer object that stores the information of people coming in.
 package com.blogspot.javasampleprogram.service;  
 import java.io.Serializable;  
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 public class PartyPeople implements Serializable {  
      public PartyPeople(){};  
      
      public PartyPeople(String name){this.name = name;};  
      
      public PartyPeople(String name, boolean isDrunk) {
        this.name = name; 
        this.drunk = isDrunk;
      };  
      public String name;  
      public boolean drunk;  
      
     /**  
       * @return the drunk  
       */  
      public boolean isDrunk() {  
           return drunk;  
      }  
      /**  
       * @param drunk the drunk to set  
       */  
      public void setDrunk(boolean drunk) {  
           this.drunk = drunk;  
      }  
      /**  
       * @return the name  
       */  
      public String getName() {  
           return name;  
      }  
      /**  
       * @param name the name to set  
       */  
      public void setName(String name) {  
           this.name = name;  
      }  
 }  
Exception thrown when the guest is not in guestlist
 package com.blogspot.javasampleprogram.exception;  
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 public class NotOnListException extends Exception {  
      public NotOnListException() {  
           super();  
      }  
      public NotOnListException(String message) {  
           super(message);  
      }  
 }  
Exception thrown when the guest becomes too drunk
 package com.blogspot.javasampleprogram.exception;  
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 public class GotTooDrunkException extends Exception {  
      public GotTooDrunkException() {  
           super();  
      }  
      public GotTooDrunkException(String message) {  
           super(message);  
      }  
 }  

Spring AOP advices :

  1. Before Advice
Before advice is given Order (1) as it should be executed before the Around advice.
Note: On the way in to a joinpoint, the advice with lowest Order value gets executed first.

 package com.blogspot.javasampleprogram.aspect;  

 import org.aspectj.lang.JoinPoint;  
 import org.aspectj.lang.annotation.Aspect;  
 import org.aspectj.lang.annotation.Before;  
 import org.springframework.core.annotation.Order;  
 import com.blogspot.javasampleprogram.exception.NotOnListException;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  

 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  

 @Aspect  
 @Order(value=1)  
 public class BeforePartyAspect {  

      /**  
       * Advice to log entering and exit of guests.  
       * @param proceedingJoinPoint  
       * @return  
       */  
      @Before(value="(execution(* com.blogspot.javasampleprogram.service.*.*(..)))")  
      public void checkGuestList(JoinPoint joinPoint) throws NotOnListException {  

           // get method arguments   
           Object[] args = joinPoint.getArgs();  

           // getting the method argument using Joinpoint API  
           PartyPeople partyPeople = (PartyPeople)args[0];  

           boolean onGuestList = false;  
           // checking guest list  
           for (int i = 0; i < partyPeoples.length; i++) {  
                if (partyPeople.getName().equals(partyPeoples[i].getName())) {  
                     onGuestList = true;  
                     break;  
                }  
           }  

           if (!onGuestList) {  
                throw new NotOnListException(partyPeople.getName()+" trying to gatecrash.");  
           }  
      }  

      PartyPeople[] partyPeoples = {new PartyPeople("jason statham"),  
                                          new PartyPeople("john travolta"),            
                                          new PartyPeople("arnold"),            
                                          new PartyPeople("christian bale"),            
                                          new PartyPeople("Vin Diesel")  
      };  
 }  


2. Around Advice

Around advice is given Order (2) as it should be executed after the Before advice

 package com.blogspot.javasampleprogram.aspect;  
 
 import java.util.Calendar;  
 import org.aspectj.lang.ProceedingJoinPoint;  
 import org.aspectj.lang.annotation.Around;  
 import org.aspectj.lang.annotation.Aspect;  
 import org.springframework.core.annotation.Order;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  
 
 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  
 @Aspect  
 @Order(value=2)  
 public class AroundPartyAspect {  
 
      /**  
       * Advice to log entering and exit of guests.  
       * @param proceedingJoinPoint  
       * @return  
       * @throws Throwable   
       */  
      @Around(value="(execution(* com.blogspot.javasampleprogram.service.*.*(..)))")  
      public void audit(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  
 
           // get method arguments   
           Object[] args = proceedingJoinPoint.getArgs();  
 
           // getting the method argument using Joinpoint API  
           PartyPeople partyPeople = (PartyPeople)args[0];  
 
          //auditing entry and exit  
           System.out.println(partyPeople.getName()+" came in at "+Calendar.getInstance().get(Calendar.HOUR_OF_DAY)+":"+Calendar.getInstance().get(Calendar.MINUTE));  

           try {  
                proceedingJoinPoint.proceed();  
           } finally {  
                // exit time kept in finally block so that even if there is any exception from method  
                // the exit time still gets audited  
                System.out.println(partyPeople.getName()+" left at "+Calendar.getInstance().get(Calendar.HOUR_OF_DAY)+":"+Calendar.getInstance().get(Calendar.MINUTE));  
           }  
      }  
 }  

3. AfterThrowing advice

Afterthrowing advice is given Order (5) as it should be executed first after the exit from the join point.
Note: On the way out from the joinpoint, the advice with highest Order value gets executed first.
 package com.blogspot.javasampleprogram.aspect;  

 import org.aspectj.lang.JoinPoint;  
 import org.aspectj.lang.annotation.AfterThrowing;  
 import org.aspectj.lang.annotation.Aspect;  
 import org.springframework.core.annotation.Order;  
 import com.blogspot.javasampleprogram.exception.GotTooDrunkException;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  

 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  

 @Aspect  
 @Order(5)  
 public class AfterThrowingPartyAspect {  

      /**  
       * Advice to send thank You Email to all guests, irrespective of whether they were   
       * too drunk or not.  
       * @param joinPoint  
       */  
      @AfterThrowing(value="(execution(* com.blogspot.javasampleprogram.service.*.*(..)))", throwing="exception")  
      public void callCabForDrunkGuests(JoinPoint joinPoint, Exception exception) {  

           if (exception instanceof GotTooDrunkException) {  
                // get method arguments   
                Object[] args = joinPoint.getArgs();  
                // getting the method argument using Joinpoint API  
                PartyPeople partyPeople = (PartyPeople)args[0];  
                System.out.println(partyPeople.getName()+" got too drunk. Calling cab!!");  
           }   
           // No need to handle NotOnListException as exceptions thrown by @Before advice never come to @AfterThrowing  
      }  
 }  

4. AfterReturning Advice

AfterReturning advice is given Order(4) as it should be executed after AfterThrowing advice and before After advice. 
 package com.blogspot.javasampleprogram.aspect;  

 import org.aspectj.lang.JoinPoint;  
 import org.aspectj.lang.annotation.AfterReturning;  
 import org.aspectj.lang.annotation.Aspect;  
 import org.springframework.core.annotation.Order;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  

 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  

 @Aspect  
 @Order(4)  
 public class AfterReturningPartyAspect {  

      /**  
       * Advice to give party gift to guests who came out without getting too drunk.  
       * @param joinPoint  
       */  
      @AfterReturning(value="(execution(* com.blogspot.javasampleprogram.service.*.*(..)))")  
      public void givePartyGift(JoinPoint joinPoint) {  

           // get method arguments   
           Object[] args = joinPoint.getArgs();  

           // getting the method argument using Joinpoint API  
           PartyPeople partyPeople = (PartyPeople)args[0];  
           System.out.println(partyPeople.getName()+" got party gift.");  
      }  
 }  

5. After advice

After advice is given Order(3) as it should be executed last on the way out.
 package com.blogspot.javasampleprogram.aspect;  

 import org.aspectj.lang.JoinPoint;  
 import org.aspectj.lang.annotation.After;  
 import org.aspectj.lang.annotation.Aspect;  
 import org.springframework.core.annotation.Order;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  

 /**  
  * @author http://java-sample-program.blogspot.in/  
  */  

 @Aspect  
 @Order(3)  
 public class AfterPartyAspect {  

      /**  
       * Advice to send thank You Email to all guests, irrespective of whether they were   
       * too drunk or not.  
       * @param joinPoint  
       */  
      @After(value="(execution(* com.blogspot.javasampleprogram.service.*.*(..)))")  
      public void sendThankYouEmail(JoinPoint joinPoint) {  
 
          // get method arguments   
          Object[] args = joinPoint.getArgs();  

          // getting the method argument using Joinpoint API  
          PartyPeople partyPeople = (PartyPeople)args[0];  
          System.out.println("Sent thank you email to "+partyPeople.getName()+". Thank you "+partyPeople.getName()+" for coming to "+joinPoint.getSignature().getName());  
      }  
 }  

Spring configuration file
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>   
  <beans xmlns="http://www.springframework.org/schema/beans"   
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    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">   

    <!-- The @AspectJ support is enabled by including the below tag -->     
    <aop:aspectj-autoproxy/>   

    <bean id="partyService"    
     class="com.blogspot.javasampleprogram.service.PartyServiceImpl" />   

   <!-- Aspect -->  
      <bean id="beforePartyAspect" class="com.blogspot.javasampleprogram.aspect.BeforePartyAspect" />  
      <bean id="aroundPartyAspect" class="com.blogspot.javasampleprogram.aspect.AroundPartyAspect" />  
      <bean id="afterThrowingPartyAspect" class="com.blogspot.javasampleprogram.aspect.AfterThrowingPartyAspect" />  
      <bean id="afterReturningPartyAspect" class="com.blogspot.javasampleprogram.aspect.AfterReturningPartyAspect" />  
      <bean id="afterPartyAspect" class="com.blogspot.javasampleprogram.aspect.AfterPartyAspect" />  

  </beans>   

Run the PartyTest to see who gets party gift and who got drunk:
 package com.blogspot.javasampleprogram.test;  

 import org.springframework.context.ApplicationContext;  
 import org.springframework.context.support.ClassPathXmlApplicationContext;  
 import com.blogspot.javasampleprogram.exception.NotOnListException;  
 import com.blogspot.javasampleprogram.service.IPartyService;  
 import com.blogspot.javasampleprogram.service.PartyPeople;  

 public class PartyTest {  

      public static void main(String[] args) {  

           ApplicationContext context1 = new ClassPathXmlApplicationContext(new String[] { "appContext.xml" });  

           PartyPeople guest1 = new PartyPeople("jason statham", false);  
           PartyPeople guest2 = new PartyPeople("john travolta", true);  
           PartyPeople guest3 = new PartyPeople("adam sandler", true);  

           System.out.println("--------------------------------------");  

           IPartyService partyService = (IPartyService)context1.getBean("partyService");  

           // in guest list and not drunk  
           try {  
                partyService.letsParty(guest1);  
           } catch (NotOnListException e) {System.out.println(e.getMessage());}  
           catch (Exception e) {}  
           System.out.println("--------------------------------------");  

           // in guest list and drunk. Even if exception is thrown still the exit time is audited  
           try {  
                partyService.letsParty(guest2);  
           } catch (NotOnListException e) {System.out.println(e.getMessage());}  
           catch (Exception e) {}  
           System.out.println("--------------------------------------");  

           // not in guest list and drunk.   
           try {  
                partyService.letsParty(guest3);  
           } catch (NotOnListException e) {System.out.println(e.getMessage());}  
           catch (Exception e) {}  
           System.out.println("--------------------------------------");  
      }  
 }  
Output:
 --------------------------------------  
 jason statham came in at 13:38  
 jason statham got party gift.  
 Sent thank you email to jason statham. Thank you jason statham for coming to letsParty  
 jason statham left at 13:38  
 --------------------------------------  
 john travolta came in at 13:38  
 john travolta got too drunk. Calling cab!!  
 Sent thank you email to john travolta. Thank you john travolta for coming to letsParty  
 john travolta left at 13:38  
 --------------------------------------  
 adam sandler trying to gatecrash.  
 --------------------------------------  

Lets analyse  the results:
Jason who was in the guest list and was not drunk was given the party gift (AfterReturning advice) and was sent thank you email (After advice) and his entry and exit time was audited (Around advice).

John was on the guest list but was drunk so he was not given party gift and cab was called for him (AfterThrowing advice) and was sent thank you email (After advice) and his entry and exit time was audited (Around advice).

Adam who was not on the guest list, was not allowed to enter the party at all (Before advice).

Conclusions

  1. On the way in to a joinpoint, the advice with lowest Order value gets executed first.
  2. On the way out from the joinpoint, the advice with highest Order value gets executed first.
  3. When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined.
Jar files used:
  1. aspectjrt-1.5.3.jar
  2. org.springframework.context-3.0.4.RELEASE.jar
  3. org.springframework.beans-3.0.3.RELEASE.jar
  4. org.springframework.core-3.0.3.RELEASE.jar
  5. org.springframework.asm-3.0.3.RELEASE.jar
  6. org.springframework.expression-3.0.3.RELEASE.jar
  7. org.springframework.aop-3.0.3.RELEASE.jar
  8. com.springsource.org.aopalliance-1.0.0.jar
  9. aspectjweaver-1.6.8.jar



3 comments:

  1. I tried your examples and added some more debug messages:

    --------------------------------------
    [1|BEFORE] checkGuestList: [jason statham - not drunk]
    [2|AROUND-BEFORE] audit: [jason statham - not drunk]
    jason statham came in at 13:17
    [2147483647|JP] letsParty: jason statham - not drunk
    [4|AFTER-RETURNING] givePartyGift: [jason statham - not drunk]
    jason statham got party gift.
    [3|AFTER] sendThankYouEmail: [jason statham - not drunk]
    Sent thank you email to jason statham. Thank you jason statham for coming to letsParty
    jason statham left at 13:17
    [2|AROUND-AFTER] audit: [jason statham - not drunk]
    --------------------------------------
    [1|BEFORE] checkGuestList: [john travolta - drunk]
    [2|AROUND-BEFORE] audit: [john travolta - drunk]
    john travolta came in at 13:17
    [2147483647|JP] letsParty: john travolta - drunk
    [5|AFTER-THROWING] callCabForDrunkGuests: [john travolta - drunk]
    john travolta got too drunk. Calling cab!!
    [3|AFTER] sendThankYouEmail: [john travolta - drunk]
    Sent thank you email to john travolta. Thank you john travolta for coming to letsParty
    john travolta left at 13:17
    [2|AROUND-AFTER] audit: [john travolta - drunk]
    --------------------------------------
    [1|BEFORE] checkGuestList: [adam sandler - drunk]
    adam sandler trying to gatecrash.
    --------------------------------------

    I put ordering value and aspect name in brackets: [order|aspect] to see execution of aspects and the ordering.

    Please check my posts:
    http://forum.springsource.org/showthread.php?126500-Mixed-ordering-of-Before-and-After-advices-does-not-work
    http://stackoverflow.com/questions/10638730/spring-aop-does-not-support-mixed-ordering-of-before-and-after-advices

    I found out that if you try to define ordering between @Before and @After aspects it is silently ignored by dedicated piece of code in AspectJPrecedenceComparator (in fact, the order remains undefined or more precisely "as defined in context") which is an error I think.

    In my case I had one @Before aspect which was able to throw an exception and I had one @AfterThrowing aspect where I wanted to catch the exception no matter if thrown from @Before aspect or joinpoint itself. The solution would be quite easy, just define higher order value for @Before aspect and lower order value for @AfterThrowing aspect (make it farther from JP) but this simply don't work and finally I had to use @Around aspect.

    ReplyDelete
  2. You can check also the Spring issue abount this which I have raised, and play with attached code: https://jira.springsource.org/browse/SPR-9438

    ReplyDelete
  3. Thanks for such a good explanation.. Even these Before Advice , After advice , Around Advice might help... have a look...

    ReplyDelete

/* */