1. Introduction

In this article, you will learn about the Around advice in Spring AOP that runs around a matched Joinpoint’s execution. Around advice is declared using the @Around annotation.

You already know about the 5 types of advice from the previous article as listed below.

2. Around advice execution – @Around

  • Around advice is declared by using the @Around annotation. It contains code which is executed before and after the matched method (JoinPoint)
  • The first parameter of the advice method must be of type ProceedingJoinPoint.
  • Within the body of the advice, calling proceed() on the ProceedingJoinPoint causes the underlying method to execute.
  • In most of the case, it is important to return a value from the advice method as the matched JoinPoint can have a return type.
Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("execution (* com.jbd.myapp.service.*.*(..))")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // Custom code before method execution
       Object retVal = pjp.proceed(); //method execution
        // Custom code after method execution
        return retVal; //exit the advice
    }
}

3. Example of Around advice – @Around

Problem statement – We will use the same UserRepository from previous examples and add a new capability to track the time taken by a method to complete its execution. Any method annotated with @ExecutionTime will log the total execution time in the console. The @ExecutionTime is a custom annotation.

Step-1: Add the dependencies

Create a maven project and add the following dependencies in the pom.xml

XML
<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>

    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>
Step-2: Create the sample classes

We will create to the methods annotated with @ExecutionTime. So we create the sample code – UserRepository, the configuration class, and the annotation as below. As we discussed before, the annotation @EnableAspectJAutoProxy is used to enable the Spring-AOP.

Java
package com.jbd.saop.around.dao;
import com.jbd.saop.around.ExecutionTime;
import org.springframework.stereotype.Repository;

//A very stupid demo repository
@Repository
public class UserRepository {

  //Add a user
  @ExecutionTime
  public UserRepository add(String username) throws InterruptedException {
    Thread.sleep(100);
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }

  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }

  //Delete an user
  @ExecutionTime
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}
Java
package com.jbd.saop.around;
import java.lang.annotation.*;

/**
 * Tracks the execution time.
 */
//Allowed to use only on methods
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExecutionTime {
}
Java
package com.jbd.saop.around;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.jbd.saop.around")
@EnableAspectJAutoProxy
public class ApplicationConfig {
}
Step-3: Create Pointcut expressions and advice

The pointcut expression matches all methods that are annotated with @ExecutionTime. If any of the methods throws RuntimeException, it will be logged. Create the aspect class as shown below.

Java
package com.jbd.saop.around.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import java.time.LocalTime;

@Configuration
@Aspect
public class ExecutionTimeLogger {

  @Around("@annotation(com.jbd.saop.around.ExecutionTime)")
  public Object logExecTime(ProceedingJoinPoint pJoinPoint){
    System.out.println("Before method: "
        + pJoinPoint.getSignature().toShortString());
    long beforeTime = System.currentTimeMillis();
    Object result = null;
    try {
       result = pJoinPoint.proceed();//Important
      //If method throws Exception or any error occurs
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
    long afterTime = System.currentTimeMillis();
    System.out.println("Time taken to execute: "
        + (afterTime - beforeTime) + "ms");
    return result;
  }
}
Step-4: Test the example

Finally, we will test the example to see its output. Add the test dependencies in the pom.xml before running the test cases.

XML
  <dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

Always remember to upgrade the maven-surefire-plugin to 3.0.0-M3 or higher when using Junit-jupiter-*.

Once you have added the test dependencies, create the Test class inside src\test.

Java
package com.jbd.saop.around;

import com.jbd.saop.around.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

@SpringJUnitConfig(ApplicationConfig.class)
public class TestAroundAdvice {

  @Autowired
  private UserRepository userRepository;

  @Test
  public void testNull(){
    Assertions.assertNotNull(userRepository);
  }

  @Test
  public void testAddUser() throws InterruptedException {
    userRepository.add("sample_username");
  }

  @Test
  public void testAddUserFailure() throws InterruptedException {
    userRepository.add(null);
  }
}

The test cases should pass with the following output in the console. The order could be different. I suggest to run each test case separately and check the output.

Output:

Before method: UserRepository.add(..)
New user added: sample_username
Time taken to execute: 124ms

Before method: UserRepository.add(..)
java.lang.RuntimeException: username is null
	at com.jbd.saop.around.dao.UserRepository.add(UserRepository.java:14)
	at com.jbd.saop.around.dao.UserRepository$$FastClassBySpringCGLIB$$dcb57c42.invoke()
...
Caused by: java.lang.NullPointerException
	... 86 more
Time taken to execute: 120ms

Conclusion:

As you can see, the around advice in Spring AOP is quite powerful. You have more control over the matched method execution, especially due to ProceedingJoinPoint. Always remember to call the proceed() inside the advice method and return the Object value. We will be discussing about Joinpoint and ProceedingJoinPoint in a separate article for more inside.

Download the working code example from GitHub code repo.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|