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 lowern
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 classesCreate 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.
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;
}
}
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);
}
}
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 {
}
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);
}
}
<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>
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.
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);
}
}
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());
}
}
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;
}
}
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() {}
}
Create a test class with the following test code and run it.
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");
}
}
======= 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.
@Aspect
@Configuration
@Order(0)
public class ZeroAdvice {
....
}
@Aspect
@Configuration
@Order(1)
public class SecondAdvice {
....
}
@Configuration
@Aspect
@Order(2)
public class ThirdAdvice {
....
}
======= 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.