Introduction

In this tutorial, you will learn about the multiple Advice ordering using @Order in Spring AOP. Often, more than one piece of advice is applied on a single matched Joinpoints and you want to control the order in which they are executed. Spring allows you to set the Precedence using org.springframework.core.Ordered interface or @Order annotation.

Rules for advice precedence

  • Within two @Order(n) annotation, the one that has lower n value has the higher precedence. E.g. The @Order(0) has higher precedence value than the @Order(1).
  • You can only use the @Order on the advice class, not on the advice method level.
  • Therefore, you can specify the order of execution when two pieces of advice written in separate classes. You can not control the order of execution if both the advice are written in a single class.
  • The highest precedence advice runs first on the way in. E.g. Within two pieces of @Before advice, one with higher precedence runs first.
  • The lowest precedence advice runs first on the way out. E.g. Given two pieces of @After advice, one with the lower precedence runs first.
  • According to the Spring official documentation the order of execution for multiple pieces of advice applied to the same Joinpoint is undefined, unless you specify the precedence. This is irrespective of whether you declare the multiple pieces of advice in a single Aspect or declare in multiple Aspects.

Example of advice ordering

I will use a simple example to demo the use of @Order annotation. Alternatively, you can extend the org.springframework.core.Ordered.

In the previous articles, I have already covered the detailed step by step approach to create a Spring AOP project. So let’s look at the code straight and understand the working of Advice Ordering.

Step-1 Create a maven project with the sample classes

Create a maven project with the following dependencies mentioned below. Create sample Dao, Service, and config classes. We will create the aspect classes in the next step.

Java
package com.jbd.saop.order.dao;

import org.springframework.stereotype.Repository;

//A very stupid demo repository
@Repository
public class UserRepository {

  //Add a user
  public UserRepository add(String username, String password) {
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    if (password == null) {
      throw new RuntimeException("password is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }

  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
 
  //Delete an user
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}
Java
package com.jbd.saop.order.dao;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Repository;

@Repository
public class EmailRepository {

  @Async
  public void sendRegistrationEmail(String email){
    System.out.println("Registration email will be sent to: " + email);
  }
}
Java
package com.jbd.saop.order;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.jbd.saop.order")
public class ApplicationConfig {
}
Java
package com.jbd.saop.order.service;

import com.jbd.saop.order.dao.EmailRepository;
import com.jbd.saop.order.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;

  @Autowired
  private EmailRepository emailRepository;

  public void registerUser(String username, String email, String password) {
    userRepository.add(username, password);
    userRepository.update(username, email);
    emailRepository.sendRegistrationEmail(email);
  }
}
XML
    <dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-core</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.5</version>
        </dependency>
    <dependencies>

   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>
Step-2 Create the Aspect class

I can only control the order of execution when pieces of the advice are in separate classes. So let us create 3 advice classes as below and the common Pointcut. Note, all the @Order annotations are commented out.

Java
package com.jbd.saop.order.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

@Aspect
@Configuration
//@Order(0)
public class ZeroAdvice {

  @Before("CommonPointcut.anyDaoMethod()")
  public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @Before() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
 
  @After("CommonPointcut.anyDaoMethod()")
  public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @After() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }

  @AfterReturning(
    pointcut = "CommonPointcut.anyDaoMethod()",
    returning = "result")
  public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    System.out.println("\n======= Inside @AfterReturning() - 0 =======");
    System.out.println(joinPoint.getSignature().toShortString());
    System.out.println("result: " + result);
  }
}
Java
package com.jbd.saop.order.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

@Aspect
@Configuration
//@Order(1)
public class SecondAdvice {

  @Before("CommonPointcut.anyDaoMethod()")
  public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @Before() - 1 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }

  @After("CommonPointcut.anyDaoMethod()")
  public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("\n======= Inside @After() - 1 =======");
    System.out.println(joinPoint.getSignature().toShortString());
  }
}
Java
package com.jbd.saop.order.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

@Configuration
@Aspect
//@Order(2)
public class ThirdAdvice {

  @Around("CommonPointcut.anyDaoMethod()")
  public Object aroundAdvice(ProceedingJoinPoint pJoinPoint) throws Throwable {
    System.out.println("\n======= Inside @Around() - 2 =======");
    System.out.println(pJoinPoint.getSignature().toShortString());
    Object result = pJoinPoint.proceed();
    System.out.println("\n======= Inside @Around() - 2 =======");
    return result;
  }
}
Java
package com.jbd.saop.order.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcut {

  @Pointcut("execution(* com.jbd.saop.order.dao.*.*(..))")
  public void anyDaoMethod() {}
}
Step-3 Run the test case and observe output

Create a test class with the following test code and run it.

Java
package com.jbd.saop.order;

import com.jbd.saop.order.dao.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(ApplicationConfig.class)
public class TestAdviceOrdering {

  @Autowired
  private UserRepository userRepository;

  @Test
  public void testAddUser() {
    userRepository.add("abc", "abc@123");
  }
}
Output:

======= Inside @Before() - 1 =======
UserRepository.add(..)

======= Inside @Around() - 2 =======
UserRepository.add(..)

======= Inside @Before() - 0 =======
UserRepository.add(..)
New user added: abc

======= Inside @After() - 0 =======
UserRepository.add(..)

======= Inside @AfterReturning() - 0 =======
UserRepository.add(..)
result: com.jbd.saop.order.dao.UserRepository@2b52c0d6

======= Inside @Around() - 2 =======

======= Inside @After() - 1 =======
UserRepository.add(..)

The order in which different advice is invoked could be different in your case. Officially the order of execution is undefined, but, it looks like the execution happened in alphabetic order.

Step-4 Apply the @Order and rerun the test case

This is what we are waiting for, now uncomment out all the @Order annotations from the advice classes and rerun the test case to observe the output.

Java
@Aspect
@Configuration
@Order(0)
public class ZeroAdvice {
 ....
}
Java
@Aspect
@Configuration
@Order(1)
public class SecondAdvice {
....
}
Java
@Configuration
@Aspect
@Order(2)
public class ThirdAdvice {
....
}
Output:

======= Inside @Before() - 0 =======
UserRepository.add(..)

======= Inside @Before() - 1 =======
UserRepository.add(..)

======= Inside @Around() - 2 =======
UserRepository.add(..)
New user added: abc

======= Inside @Around() - 2 =======

======= Inside @After() - 1 =======
UserRepository.add(..)

======= Inside @After() - 0 =======
UserRepository.add(..)

======= Inside @AfterReturning() - 0 =======
UserRepository.add(..)
result: com.jbd.saop.order.dao.UserRepository@7de0c6ae

Conclusion:

You can use the @Order annotation to control the order of advice executions when they are separated into multiple classes. If they are written in a single class, you should think of combining them. You can download the complete working example from Github repo.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|

Table of Contents