无法为同一方面的方法设置建议class
Can not set advice for the methods of the same aspect class
我正在尝试通过构建小型银行交易模拟来学习 SpringAOP 和 AspectJ。但是我无法向方面 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
有几点需要考虑。
Bank
和 BankAspect
用 @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.
我正在尝试通过构建小型银行交易模拟来学习 SpringAOP 和 AspectJ。但是我无法向方面 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
有几点需要考虑。
Bank
和 BankAspect
用 @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
@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.