无法为同一方面的方法设置建议class

Can not set advice for the methods of the same aspect class

我正在尝试通过构建小型银行交易模拟来学习 SpringAOPAspectJ。但是我无法向方面 class 本身的方法添加建议(@Before、@After、@AfterThrowing)。

这是模型

Bank.java

@Component
public class Bank {
    private int balance;
    private int pinCode;
    private int tempPin;

    public int getBalance() {
        return balance;
    }

    @Value("10000")
    public void setBalance(int balance) {
        this.balance = balance;
    }

    public int getPinCode() {
        return pinCode;
    }

    @Value("6789")
    public void setPinCode(int pinCode) {
        this.pinCode = pinCode;
    }

    public int getTempPin() {
        return tempPin;
    }

    public void setTempPin(int tempPin) {
        this.tempPin = tempPin;
    }

    public void withDraw(int amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Successful Withdraw");
        } else {
            System.out.println("Insufficient Balance");
        }
    }
}

这是方面class

BankAspect.java

@Component
@Aspect
public class BankAspect {
    private Bank bank;

    public Bank getBank() {
        return bank;
    }

    @Autowired
    public void setBank(Bank bank) {
        this.bank = bank;
    }

    @Before("execution(public void dev.ritam.model.Bank.withDraw(..))")
    public void validatePin() {
        if (bank.getPinCode() != bank.getTempPin()) {
            throw new RuntimeException();
        }
    }

    @AfterThrowing("execution(public void dev.ritam.aspect.BankAspect.validatePin(..))")
    public void logException() {
        System.out.println("Wrong Pin");
    }
}

这是配置class

AppConfig.java

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("dev.ritam")
public class AppConfig {
    @Bean
    Bank bank() {
        return new Bank();
    }

    @Bean
    BankAspect bankAspect() {
        return new BankAspect();
    }
}

这是主要方法

App.java

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        Bank bank = context.getBean(Bank.class);

        try {
            bank.setTempPin(1234);
            bank.withDraw(1000000);
        } catch (Exception ignore) {
        }
    }
}

只有 validatePin() @Before 个建议正在执行。我想得到 'Wrong Pin' 作为输出,但是 @AfterThrowing 建议没有被识别。

方法public void validatePin()(在classBankAspect中)充当建议;因此,如果您单独使用 AspectJ ,您将无法像现在这样拦截它(例如, 您需要使用切入点 adviceexecution)。在 Spring AOP 的情况下,事情更加严格; Spring AOP 不允许方面成为 建议 的目标。

要解决您的问题,您可以拦截与 before 切入点相同的方法,即:

@AfterThrowing("execution(public void dev.ritam.model.Bank.withDraw(..))")
public void logException() {
    System.out.println("Wrong Pin");
}

因为异常将在方法范围内抛出 Bank.withDraw 您的 @AfterThrowing 切入点将能够捕获该异常。

仅适用于 AspectJ

一个简单的解决方案是包装这段代码:

  if (bank.getPinCode() != bank.getTempPin()) {
        throw new RuntimeException();
    }

进入一个方法,例如:

public void check_pin_code() {
    if (bank.getPinCode() != bank.getTempPin()) {
        throw new RuntimeException();
    }
}

现在拦截该方法:

@AfterThrowing("execution(public void dev.ritam.aspect.BankAspect.check_pin_code(..))")
public void logException() {
    System.out.println("Wrong Pin");
}

另一种方法,但必须小心使用,是使用 adviceexecution()

@AfterThrowing("within(BankAspect) && adviceexecution()")
public void logException() {
    System.out.println("Wrong Pin");
}

这将拦截在 BankAspect 内发生并导致抛出异常的所有 建议 执行。

来自参考 documentation :

Advising aspects with other aspects? In Spring AOP, aspects themselves cannot be the targets of advice from other aspects. The @Aspect annotation on a class marks it as an aspect and, hence, excludes it from auto-proxying.

您无法在 Spring AOP 中建议某个方面,这就是 @AfterThrowing 未执行的原因。

要抛出异常并记录消息,您可以使用相同的方法 Bank.withDraw()@AfterThrowing。这样,validatePin() 通知将在 Bank.withDraw() 之前执行,如果验证抛出异常,logException() 通知将被执行。这是@dreamcash 的回答中提到的第二个选项。

示例代码

@Before("execution(public void dev.ritam.model.Bank.withDraw(..))")
public void validatePin() {
    if (bank.getPinCode() != bank.getTempPin()) {
        throw new RuntimeException();
    }
}

@AfterThrowing("execution(public void dev.ritam.model.Bank.withDraw(..))")
public void logException() {
    System.out.println("Wrong Pin");
}

这将导致以下控制台输出序列

Wrong Pin
java.lang.RuntimeException

有几点需要考虑。

BankBankAspect@Component 注释并且 @ComponentScan 会将它们注册为 beans。这意味着不需要在 AppConfig 中使用 @Bean 注册的 bean。两者中的任何一个都是必需的。

@dreamcrash 的回答中提到的最后一个选项不受 Spring AOP 支持:请参阅以 其他切入点类型

开头的部分

更新:

这是一个棘手的问题,我建议的答案是基于 Spring 引导版本:2.2.6.RELEASE 它使用 spring 版本的库:5.2.5.RELEASE

OP 的 code shared 基于 Spring 版本:5.3.1,建议未按预期执行。

共享代码中的 Spring AOP 建议按预期工作,spring 版本应 <= 5.2。6.RELEASE

请更新来自

的pom.xml条目
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.3.1</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>

为了快速参考,以下是不适用于版本 5.3.1

的 OP 代码
@After("execution(public void dev.ritam.model.Bank.setTempPin(..))")
public void validatePin() {
    if (bank.getPinCode() != bank.getTempPin()) {
        throw new RuntimeException("Wrong Pin");
    } else {
        System.out.println("Correct Pin");
    }
}

@AfterThrowing(value = "execution(public void dev.ritam.model.Bank.setTempPin(..))", throwing = "e")
public void logException(Exception e) {
    System.out.println(e.getMessage());
}

不确定是否记录了这种行为变化,或者这是一个错误。 如果我找到任何关于此的任何进一步信息,将会更新。

信息:

这似乎是一个框架问题。此处有更多详细信息:

https://github.com/spring-projects/spring-framework/issues/26202

来自 reference documentation 的相关位,

As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing. Note, however, that due to the implementation style in Spring’s AspectJAfterAdvice, an @After advice method will effectively be invoked after any @AfterReturning or @AfterThrowing advice methods in the same aspect.