在 Spring AOP 中使用 @Order 通知排序

简介
在本文中,您将了解在 Spring AOP 中使用 @Order 的多个 Advice 排序。通常,不止一条建议应用于单个匹配的连接点,并且您希望控制它们的执行顺序。Spring 允许您使用 org.springframework.core.Ordered
接口或@Order
注解设置优先级。
通知优先规则
在两个 @Order(n)
注释中,具有较低 n 值的注释具有较高的优先级。例如。 @Order(0) 的优先级值高于@Order(1)。
您只能在通知类上使用@Order,而不能在通知方法级别上使用。
因此,当两条通知写在不同的类中时,您可以指定执行顺序。如果两个通知都写在一个类中,您将无法控制执行顺序。
最高优先级的通知首先在进入的过程中运行。例如在两条@Before 建议中,优先级较高的一条首先运行。
最低优先级的通知在退出时首先运行。例如。给定两条@After 建议,优先级较低的一条首先运行。
根据 Spring 官方文档,应用到同一个 Joinpoint 的多个通知的执行顺序是未定义的,除非您指定优先级。这与您是在单个 Aspect 中声明多条通知还是在多个 Aspect 中声明无关。
通知顺序示例
我将使用一个简单的例子来演示@Order
注解的使用。或者,您可以扩展 org.springframework.core.Ordered。
在之前的文章中,我已经介绍了创建 Spring AOP 项目的详细分步方法。因此,让我们直接看代码并了解 Advice Ordering 的工作原理。
步骤一 使用示例类创建一个 maven 项目
使用下面提到的以下依赖项创建一个 Maven 项目。创建示例 Dao、Service 和 config 类。我们将在下一步中创建方面类。
UserRepository
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;
}
}
EmailRepository
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);
}
}
ApplicationConfig
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 {
}
UserService
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);
}
}
pom.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>
步骤 2 创建 Aspect 类
当通知的代码片段位于不同的类中时,我只能控制执行顺序。因此,让我们创建如下 3 个通知类和常见的切入点。注意,所有的 @Order 注释都被注释掉了。
ZeroAdvice
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);
}
}
SecondAdvice
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());
}
}
ThirdAdvice
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;
}
}
CommonPointcut
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() {}
}
步骤3 运行测试用例并观察输出
使用以下测试代码创建一个测试类并运行它。
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(..)
在案例中,调用不同建议的顺序可能会有所不同。正式执行的顺序是未定义的,但是,看起来执行是按字母顺序发生的。
步骤4 应用@Order
并重新运行测试用例
这就是我们所期待的,现在取消注释掉通知类中的所有 @Order 注释并重新运行测试用例以观察输出。
ZeroAdvice.java
@Aspect
@Configuration
@Order(0)
public class ZeroAdvice {
....
}
SecondAdvice.java
@Aspect
@Configuration
@Order(1)
public class SecondAdvice {
....
}
ThirdAdvice.java
@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
总结
您可以使用 @Order 注释来控制通知执行的顺序,当它们被分成多个类时。如果它们是在一个类中编写的,您应该考虑将它们组合起来。