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 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.
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>
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.
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() {} }
Step-3 Run the test case and observe output
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"); } }
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.
@Aspect @Configuration @Order(0) public class ZeroAdvice { .... }
@Aspect @Configuration @Order(1) public class SecondAdvice { .... }
@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.
Leave A Comment