【问题标题】:Can not set advice for the methods of the same aspect class不能为同一方面类的方法设置建议
【发布时间】:2020-11-27 05:29:36
【问题描述】:

我正在尝试通过构建一个小型银行交易模拟来学习 SpringAOPAspectJ。但我无法向方面类本身的方法添加建议(@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");
        }
    }
}

这是方面类

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");
    }
}

这是配置类

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 建议未被识别。

【问题讨论】:

    标签: java spring maven dependency-injection spring-aop


    【解决方案1】:

    来自参考documentation

    建议方面与其他方面? 在 Spring AOP 中,切面本身 不能成为其他方面的建议对象。 @Aspect 类上的注释将其标记为一个方面,因此将其排除在外 来自自动代理。

    你不能在 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 会将它们注册为 bean。这意味着不需要在 AppConfig 中使用 @Bean 注册的 bean。两者中的任何一个都是必需的。

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

    开头的部分

    更新

    这是一个棘手的问题,我建议的答案是基于 Spring Boot 版本:2.2.6.RELEASE 它使用来自版本的弹簧库:5.2.5.RELEASE

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

    共享代码中的 Spring AOP 建议可以按预期工作,spring 版本应该是

    请从

    更新 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>
    

    为了快速参考,以下是 OP 的代码,它不适用于版本 5.3.1

    @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 中的相关位,

    Spring Framework 5.2.7 开始,在同一个版本中定义的通知方法 分配需要在同一连接点运行的@Aspect 类 根据他们的建议类型按以下顺序排列优先级,从 从最高到最低优先级:@Around、@Before、@After、 @AfterReturning,@AfterThrowing。但请注意,由于 Spring 的 AspectJAfterAdvice 中的实现风格,@After 通知 方法将在任何 @AfterReturning 或 @AfterThrowing 在同一方面的建议方法。

    【讨论】:

    • 我尝试了您提供的示例代码。没有成功。
    • 什么不起作用?您是否从“忽略”对象打印错误堆栈?在给出答案之前测试了代码
    • 这应该可以。因为 OP 想要在同一方法之前和之后做一些事情,所以另一种选择是只使用 @Around 建议来进行验证和错误记录。不过,这是代码一致性和关注点分离之间的权衡。无论如何,这个例子似乎不是“真实世界”。可能您会有一个单独的通用异常记录器方面,并将原因“错误 PIN”放入运行时异常的构造函数中它所属的位置,即记录异常也会自动记录正确的原因以及堆栈跟踪。
    • @R.G 我不是在评判你或任何东西,也许我缺乏这方面的知识。我制作了一个repo,这与我发布的问题略有不同。但仍然有同样的问题。我已经尝试过您的解决方案 r_g_answer 分支。请指出我的错误。谢谢。
    • @RitamChakraborty 如果我的回复显得粗鲁并以某种方式冒犯了你,我很抱歉。 “它不起作用”没有传达任何有用的信息,我的问题是“什么不起作用?”。我也是来这里学习的,在我发表评论后回答这个问题的人是我这里的大师。让我调试你的代码,看看我是否可以改进答案。感谢您提供示例代码
    【解决方案2】:

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

    为了解决您的问题,您可以拦截 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 中发生并导致抛出异常的advice 执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      • 2019-02-09
      • 2017-10-19
      • 1970-01-01
      相关资源
      最近更新 更多