静下心来的个人专栏
上一篇

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

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

在 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 注释来控制通知执行的顺序,当它们被分成多个类时。如果它们是在一个类中编写的,您应该考虑将它们组合起来。

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们