I am trying to learn SpringAOP with AspectJ, by building a small bank transaction simulation. But I am unable to add advice (@Before, @After, @AfterThrowing) to the methods of the aspect class itself.
This is the model
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");
}
}
}
This is the aspect 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");
}
}
This is the configuration class
AppConfig.java
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("dev.ritam")
public class AppConfig {
@Bean
Bank bank() {
return new Bank();
}
@Bean
BankAspect bankAspect() {
return new BankAspect();
}
}
This is the main method
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) {
}
}
}
Only validatePin()
@Before
advice is getting executed. I am suppose to get 'Wrong Pin' as output, but the @AfterThrowing
advice is not being recognized.
From the reference 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.
You cannot advice an aspect in Spring AOP and that is the reason for @AfterThrowing
not executing.
To throw the exception and to log the message , you can advice the same method Bank.withDraw()
with @AfterThrowing
. That way, the validatePin()
advice will get executed before Bank.withDraw()
and logException()
advice will get executed if an exception was thrown from validation. This is the second option mentioned in the answer from @dreamcash.
Example code
@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");
}
This would result in the following sequence of console output
Wrong Pin
java.lang.RuntimeException
Couple of points to consider.
Bank
and BankAspect
are annotated with @Component
and @ComponentScan
would register them as beans. Which means beans registered with @Bean
in AppConfig is not required. Either of the two is required.
The last option mentioned in @dreamcrash's answer is not supported through Spring AOP : Refer section starting with Other pointcut types
Update:
This was tricky one , the answer I suggested was based on Spring Boot Version : 2.2.6.RELEASE which uses spring libraries from version : 5.2.5.RELEASE
The code shared by OP is based on Spring Version : 5.3.1 and the advices are not executing as expected.
The Spring AOP advices in the code shared to work as expected, the spring version should be <= 5.2.6.RELEASE
Please update the the pom.xml entry from
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
to
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
For quick reference, following is OP's code that is not working with version 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());
}
Not sure if this change in behaviour is documented or not or this is a bug. Any further information on this will be updated if I find any.
Info:
This appears to be a framework issue . More details here:
https://github.com/spring-projects/spring-framework/issues/26202
Relevant bits from the 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.