Spring AOP has come up with different annotations namely @Before, @After, @Around,@AfterReturning, and @AfterThrowing. Usually, confusions occur among developers were to write the apt code among these annotations. In this article, let us see them by using a sample application.
@Around
This is the most effective advice among all other advice. The first parameter is of type ProceedingJoinPoint. Code should contain proceed() on the ProceedingJoinPoint and it causes the underlying lines of code to execute. It contains the code that has to be executed before and after when the method is matched with the pointcut
Sample snippet
Main important thing is that even if there are @Before annotations, @Around will be invoked first with the before invocation and then only @Before will be called.
Java
// Displays all the available methods i.e. the advice will // be called for all the methods The method declaration is // called the pointcut signature. It provides a name that can // be used by advice annotations to refer to that pointcut. @Pointcut ( value = "execution(* com.examples.service.GeneralService.*(..))" ) private void logDisplay() { } // Declares the around advice that is applied before and // after the method matching with a pointcut expression Even // there are @Before annotations, @Around will be invoked // first with the before invocation and then only @Before // will be called @Around (value = "logDisplay()" ) public void aroundAdvice(ProceedingJoinPoint jp) throws Throwable { System.out.println( "The method aroundAdvice() before invocation of the method " + jp.getSignature().getName() + " method" ); try { jp.proceed(); } finally { } System.out.println( "The method aroundAdvice() after invocation of the method " + jp.getSignature().getName() + " method" ); } |
Hence this is always an ideal and good place to perform the business logic and it will help to inform what is happening before and after the application when the code runs.
Possible Use-cases
- When the user is downloading an image in an application
- When an employee logs in into the office on daily basis.
- When an external application like a printer/scanner started its job in the specified time etc.
In all these scenarios, @Around advice helps to provide the logs before and after, and also the service also runs perfectly.
@Before
This advice will run as a first step if there is no @Around advice. If @Around is there, it will run after the beginning portion of @Around.
Sample snippet
Java
// If there is no @Around advice, @Before will be called // first, otherwise @Around Before Invocation is called @Before ( "execution(* com.examples.service.GeneralService.*(..))" ) public void logBefore() { System.out.println( ".............I WILL EXECUTE BEFORE EACH AND EVERY METHOD............." ); } |
Possible scenario to have @Before:
For example, if there are two tables(parent and child) and when there is an insert happening in the child table, we need to check whether already the corresponding parent exists or not. It can be checked with @Before
@After
This advice will run as a step after @Before advice if there is no @Around advice. If @Around is there, it will run after the ending portion of @Around.
Sample snippet
Java
// If there is no @Around advice, @After will be called // after @Before(if available) first, otherwise @Around After // Invocation is called @After ( "execution(* com.examples.service.GeneralService.*(..))" ) public void logAfter() { System.out.println( ".............I WILL EXECUTE AFTER EACH AND EVERY METHOD............." ); } |
Possible scenario to have @After:
For example, if an image or an mp4 is getting downloaded, we need to inform the end-users about the download process is complete. They are all handled at @After
@AfterReturning
This advice will run as a step after @After advice. Usually, this is the place , where we need to inform about the successful resultant of the method. If the method successSnippetfully returned back, @AfterReturning will have a place to indicate the results.
Sample Snippet:
Java
// implementing after returning advice // This is generally used to indicate the output after // successful return of the method, will be called at last // i.e. after @Around @AfterReturning ( value = "execution(* com.examples.service.GeneralService.*(..))" , returning = "account" ) public void afterReturningAdvice(JoinPoint joinPoint) { System.out.println( "After Returning method:" + joinPoint.getSignature()); } |
Possible scenario to have @AfterReturning:
The result of the method either success/failure, its root causes, and the final result are all can be mentioned here.
@AfterThrowing
This step will be executed whenever there is an exception in the code. We need to handle that by putting try-catch blocks and always it is a good practice to handle exceptions. In AOP, by means of @AfterThrowing, it is handled.
Sample snippet:
Java
// implementing after throwing advice // This is generally used to indicate the exception in case // of exception , will be called whenever exception occurs @AfterThrowing ( value = "execution(* com.examples.service.GeneralService.*(..))" , throwing = "ex" ) public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) { System.out.println( "After Throwing exception in method:" + joinPoint.getSignature()); System.out.println( "Exception is:" + ex.getMessage()); } |
Possible scenario to have @AfterThrowing:
Whenever there are Checked exceptions like IOExceptions (File not found when trying to download) or Unchecked exceptions like Arithmetic Exception (division by zero due to some calculations), this is the place where the code will enter and perform the steps which are present here.
By combining all the above advice, let us build a sample maven project to hold all advice.
Example Project
Project structure:
pom.xml
XML
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.2.2.RELEASE</ version > < relativePath /> </ parent > < groupId >com.examples</ groupId > < artifactId >aop-different-advice-example</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >aop-around-advice-example</ name > < description >Demo project for Spring Boot</ description > < properties > < java.version >1.8</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > <!-- This is the much required dependency to have various advices --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-aop</ artifactId > </ dependency > <!-- For testing --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > < exclusions > < exclusion > < groupId >org.junit.vintage</ groupId > < artifactId >junit-vintage-engine</ artifactId > </ exclusion > </ exclusions > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
Let us see the java part
DifferentAdviceExampleApplication.java
Java
import com.examples.service.GeneralService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication // @EnableAspectJAutoProxy annotation enables support for // handling the components marked with @Aspect annotation. @EnableAspectJAutoProxy public class DifferentAdviceExampleApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run( DifferentAdviceExampleApplication. class , args); // Fetching the object from the application // context. GeneralService service = context.getBean(GeneralService. class ); // checking for an employee available in the // organization String employeeNumber = "A123" ; try { service.checkEmployeeExistence(employeeNumber); } catch (Exception ex) { System.out.println( "Exception occurred.." + ex.getMessage()); } // Displaying balance in the account. String accnumber = "10000" ; try { service.getBalance(accnumber); } catch (Exception ex) { System.out.println( "Exception occurred.." + ex.getMessage()); } // Closing the context object. context.close(); } } |
DifferentAspect.java
Java
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; // Enables the spring AOP functionality in an application @Aspect @Component public class DifferentAspect { // Displays all the available methods i.e. the advice // will be called for all the methods The method // declaration is called the pointcut signature. It // provides a name that can be used by advice annotations // to refer to that pointcut. @Pointcut ( value = "execution(* com.examples.service.GeneralService.*(..))" ) private void logDisplay() { } // If there is no @Around advice, @Before will be called // first, otherwise @Around Before Invocation is called @Before ( "execution(* com.examples.service.GeneralService.*(..))" ) public void logBefore() { System.out.println( ".............I WILL EXECUTE BEFORE EACH AND EVERY METHOD............." ); } // If there is no @Around advice, @After will be called // after @Before(if available) first, otherwise @Around // After Invocation is called @After ( "execution(* com.examples.service.GeneralService.*(..))" ) public void logAfter() { System.out.println( ".............I WILL EXECUTE AFTER EACH AND EVERY METHOD............." ); } // implementing after returning advice // This is generally used to indicate the output after // successful return of the method, will be called at // last i.e. after @Around @AfterReturning ( value = "execution(* com.examples.service.GeneralService.*(..))" , returning = "account" ) public void afterReturningAdvice(JoinPoint joinPoint) { System.out.println( "After Returning method:" + joinPoint.getSignature()); // System.out.println(account); } // implementing after throwing advice // This is generally used to indicate the exception in // case of exception , will be called whenever exception // occurs @AfterThrowing ( value = "execution(* com.examples.service.GeneralService.*(..))" , throwing = "ex" ) public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) { System.out.println( "After Throwing exception in method:" + joinPoint.getSignature()); System.out.println( "Exception is:" + ex.getMessage()); } // Declares the around advice that is applied before and // after the method matching with a pointcut expression // Even there are @Before annotations, @Around will be // invoked first with the before invocation and then only // @Before will be called @Around (value = "logDisplay()" ) public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println( "The method aroundAdvice() before invocation of the method " + proceedingJoinPoint.getSignature().getName() + " method" ); try { proceedingJoinPoint.proceed(); } finally { } System.out.println( "The method aroundAdvice() after invocation of the method " + proceedingJoinPoint.getSignature().getName() + " method" ); } } |
GeneralService.java
Java
import org.springframework.stereotype.Service; @Service public class GeneralService { public void getBalance(String accNum) { System.out.println( "Inside getBalance() method" ); if (accNum.equals( "12345" )) { System.out.println( "Total balance: ......" ); } else { System.out.println( "Sorry! wrong account number." ); } } // second method // we can write any methods and when called, all methods // follow the same flow public String checkEmployeeExistence(String employeeNumber) { System.out.println( "Inside checkEmployeeExistence() method" ); String status = null ; if (employeeNumber.equals( "A123" )) { System.out.println(employeeNumber + " is currently active" ); status = "active" ; } else { System.out.println(employeeNumber + " is currently inactive" ); status = "Inactive" ; } return status; } } |
Program execution and output:
Output:
Explanation:
1. checkEmployeeExistence method // @around advice begins The method aroundAdvice() before invocation of the method checkEmployeeExistence method // @before advice .............I WILL EXECUTE BEFORE EACH AND EVERY METHOD............. // execution steps Inside checkEmployeeExistence() method A123 is currently active // @around advice ends The method aroundAdvice() after invocation of the method checkEmployeeExistence method // @after advice .............I WILL EXECUTE AFTER EACH AND EVERY METHOD............. @afterreturning After Returning method:String com.examples.service.GeneralService.checkEmployeeExistence(String) // Similarly it will be done for getBalance
Let us see when an exception throws for getBalance method by just passing accountnumber to null
Java
// Displaying balance in the account. String accnumber = null ; try { service.getBalance(accnumber); } catch (Exception ex) { System.out.println( "Exception occurred.." + ex.getMessage()); } |
Explanation:
// @around advice begins The method aroundAdvice() before invocation of the method getBalance method @before advice .............I WILL EXECUTE BEFORE EACH AND EVERY METHOD............. // execution steps Inside getBalance() method // An exception is occurred and hence it will come out and print data from @after advice .............I WILL EXECUTE AFTER EACH AND EVERY METHOD............. @AfterThrowing advice After Throwing exception in method:void com.examples.service.GeneralService.getBalance(String) Exception is:Cannot invoke "String.equals(Object)" because "accNum" is null Exception occurred..Cannot invoke "String.equals(Object)" because "accNum" is null