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

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

相关推荐

Python3.5使用subprocess.run调用外部程序

Python 3.5的subprocess模块新增了run()函数,大部分调用子进程的场景都推荐使用run()函数,一些高级的用法则可以直接调用Popen 接口。run()函数run函数常用参数如下:run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=N

Spring Boot使用springProfile实现Logback多环境的通用配置

在一个基于Spring boot开发的项目里,常常需要有多套环境的配置:开发,测试以及产品。这里给出一个logback的通用配置。在src/main/resources目录下创建配置文件logback-spring.xml,多环境的通用配置内容如下:<?xml version="1.0" encoding="UTF-8"?

Java 8忽略大小写排序字符串

假如有一个Student对象的列表students,现在需要对Student对象的name名字不区分大小写排序。方法一在java 8里可以使用列表的sort方法,也可以使用stream().sorted,并结合Comparator.comparing做排序List<Student> students = ...students.sort(Comparator.comparing(Stu

C#对List的元素按属性排序

C#对List元素排序有几种方法。方法一、使用LinqList<User> sortedList = list.OrderBy(o=>o.ID).ToList();如果按降序排序,可以使用OrderByDescending方法:List<User> sortedList = list.OrderByDescending(o=>o.ID).ToList();方法二

二叉树的中序遍历

中序遍历 中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。            R / A B / / C D E F 上面这个树的中序遍历顺

ES6中async的使用案例

在项目中有时会遇到异步操作的问题,async就是解决异步操作的终极操作。我会以终极三问(what,why,when)的形式来说明什么是async。由于这是第一篇文章不知道怎么写,有很大部分是借鉴阮一峰老的原文,事例将会从我的项目中摘取。 async是什么? 官方例子 官方文档 async相当于对Generator 函数的一个语法糖const fs = require

Python3 词典按值排序的方法

Python 3.6按值排序:x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}{k: v for k, v in sorted(x.items(), key=lambda item: item[1])}{0: 0, 2: 1, 1: 2, 4: 3, 3: 4}按键排序只需要把item[1]改为item[0]x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}{