Spring AOP using AspectJ

Spring AOP using AspectJ | AOP understands for Aspect Oriented Programming. Why do we use Spring AOP? For cross-cutting concerns. Let us see it in detail.

Cross-cutting concern: Move additional services of a project into other classes (services class/Aspect classes) and bind them when and where they are required.

AOP Terminologies

  1. Aspect (@Aspect): Aspect is a class that provides additional services to projects like transaction management, logging, security, encoding, decoding, etc.
  2. Advice: It is a method inside the Aspect class. It is the actual implementation of Aspect. There are 5 Types of Advice:-
    1. Before Advice (@Before): Executing advice before calling the business method.
    2. After Advice (@After): Executing advice after the business method is finished.
    3. Around Advice (@Around): Advice code made into 2 sections/parts, 1st part executed before advice and then business method and at last 2nd part of advice.
    4. After Returning Advice (@AfterReturing): After executing the business method, only on success execute advice.
    5. After Throwing Advice (@AfterThrowing): After executing the business method, if the business method is throwing any exception then execute advice.
  3. Pointcut (@Pointcut): It is an expression, that will select the business method that needs advice. It can never specify what advice. It looks like a method header.
  4. JoinPoint: It is the combination of advices & pointcut. It simply connects business class methods with required advice.
  5. Target: Business class object where advice will be applied.
  6. Weaving: Using JDK dynamic proxy, a new class will be generated which contains both business method and connected advice. Weaving is a process of mixing business class methods and their connected advice.
  7. Proxy: The final output (class/object) is called a proxy that contains both logic connected.
@Aspect
public class TxnService {

    @Pointcut("execution(public * com.app.ProductDao.*(...))")
    public void p1() {}

    @Before("p1()") // JoinPoint
    public void beginTx() {
        // ...
    }
}

What is the difference between @After, @AfterReturing, @AfterThrowing advice?

  • After advice (@After) is executed next to the business method without caring about business method whether it was executed successfully or failed.
  • After returning advice (@AfterReturing) is executed only on successful execution of the business method.
  • After throwing advice (@AfterThrowing) is executed only on failure (Exception) execution of the business method.

How to implement the AOP concept?
It can be implemented in 2 ways:-

  1. Spring AOP using XML Based Configuration (legacy style)
  2. Spring AOP using AspectJ (Annotations based)

Pointcut

  • A pointcut is an expression, that will select the business class method that needs advice.
  • Pointcut can never specify which advice is going to be connected.

Pointcut syntax:-
Specifier ReturnType Package.ClassName.MethodName(ParameterTypes)

Note:- Allowed symbols in pointcut expression: *(star), .(dot)

Examples

1) public int com.kp.dao.EmployeeDao.saveEmployee(Employee)
The saveEmployee() method having parameter “Employee” with return type “int” of type public, defined in class EmployeeDao from com.kp.dao package is selected to connect with advice.

2) public * com.kp.dao.EmployeeDao.*()
Zero parameters only (no parameter)
Any method name/Method inside the EmployeeDao class
Any return type

3) public * com.kp.dao.EmployeeDao.*(..)
Two dots mean zero or more params (of any type), therefore any parameter is fine.

4) public * com.kp.dao.*.*(..)
All classes that are inside the com.kp.dao package and their methods.

5) * com.kp.dao.*.*(..)
Any return type and any accessibility modifier.

Let us understand it in detail through an example.

Business Methods:-

  • M#1 public int saveEmployee(Employee e) { ... }
  • M#2 public void deleteEmployee(Integer id) { ... }
  • M#3 public void updateEmployee(Employee e) { ... }
  • M#4 public Employee getEmployee(Integer id) { ... }

Pointcut Expressions Examples

a) public * *()
[Zero params, any method name, and any return type].
Number of methods matching: zero

b) public void *(..)
[Two dots inside method param indicates any number of/type of parameters]
Number of methods matching: 2 => M#2 & M#3

c) public * saveEmployee(..)
Number of methods matching: 1 => M#1

d) public * *(Integer)
Number of methods matching: 2 => M#2 & M#4

Spring AOP Example

Create a spring starter project “SpringBootAOPEx”. Add the following dependencies:-

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Let us see a very basic example.

package com.example.demo;

import org.springframework.stereotype.Component;

// business class
@Component
public class EmployeeDao {
    public void saveEmployee() {
        System.out.println("EmployeeDao.saveEmployee()");
    }
}
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class TestRunner implements CommandLineRunner {

    @Autowired
    EmployeeDao dao;

    @Override
    public void run(String... args) throws Exception {
        dao.saveEmployee();
    }

}

Output, when we run the application:-

Now, without modifying EmployeeDao and TestRunner classes we want to execute some tasks before the saveEmployee() method call. To do that:-

  1. In the starter class, add @EnableAspectJAutoProxy
  2. Create an Aspect as follows and do not forget to add @Component to the class:-
package com.example.demo;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TxnService {

    @Pointcut("execution(public void com.example.demo.EmployeeDao.saveEmployee())")
    public void p1() {
    }

    @Before("p1()") // JoinPoint
    public void beginTxn() {
        System.out.println("Txn Begin");
    }
}

Now, when we run the application the output:-

The same can be done as follows:-

@Aspect
@Component
public class TxnService {

    @Before("execution(public String com.example.demo.EmployeeDao.*())")
    public void beforeTxn() {
        System.out.println("Txn Begin");
    }

}

More Examples of Spring AOP

package com.example.demo;

import java.util.Random;

import org.springframework.stereotype.Component;

// business class
@Component
public class EmployeeDao {
    public void saveEmployee() {
        System.out.println("EmployeeDao.saveEmployee()");

        if (new Random().nextInt(15) <= 5) {
            throw new RuntimeException("Dummy Exception");
        }
    }
}
package com.example.demo;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TxnService {

    @Pointcut("execution(public void com.example.demo.EmployeeDao.saveEmployee())")
    public void p1() {
    }

    @Before("p1()")
    public void beginTxn() {
        System.out.println("Txn Begin");
    }

    @AfterReturning("p1()")
    public void commitTxn() {
        System.out.println("Txn is committed");
    }

    @AfterThrowing("p1()")
    public void rollbackTxn() {
        System.out.println("Txn is rollback");
    }

    @After("p1()")
    public void sendReport() {
        System.out.println("Report sent");
    }
}

Output (in case of failure):-

Output (in case of success):-

The @After will always execute whether the business method is executed successfully or failed.

We can also display the exception info in advice.

@AfterThrowing(value = "p1()", throwing = "th")
public void rollbackTxn(Throwable th) {
    System.out.println("Txn is rollback: " + th.getMessage());
}

Similarly, if the business method returns something, we can get that in advice.

@Component
public class EmployeeDao {
    public String saveEmployee() {
        // existing code
        return "Hello";
    }
}

The saveEmployee() method is returning a string therefore the pointcut should also have String return type execution.

@Aspect
@Component
public class TxnService {

    @Pointcut("execution(public String com.example.demo.EmployeeDao.saveEmployee())")
    public void p1() {
    }

    @AfterReturning(value = "p1()", returning = "ob")
    public void commitTxn(Object ob) {
        System.out.println("Txn is committed: " + ob);
    }
}

Output:-

Spring Boot AOP – Around Advice Example

@Component
public class EmployeeDao {
    public String saveEmployee() {
        System.out.println("EmployeeDao.saveEmployee()");
        return "Hello";
    }
}
package com.example.demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TxnService {

    @Pointcut("execution(* com.example.demo.EmployeeDao.*())")
    public void p1() {
    }

    @Around("p1()")
    public void aroundTest(ProceedingJoinPoint jp) {
        System.out.println("Before business method");

        // call business method
        try {
            Object obj = jp.proceed();
            System.out.println("Return data: " + obj);
            // on success
        } catch (Throwable e) {
            // on failures
            e.printStackTrace();
        }

        System.out.println("After business method");
    }

}

Output:-

From advice, we need to call the business method by using joinpoint details, i.e., ProceedingJoinPoint. It has a method proceed() that makes a call to the business method. Even it returns value given by business method.

We can even return the value to the controller. If the method is called from the controller, then @Around can suppress the return value from the service. But we can return that from the advice as follows:-

@Around("p1()")
public String aroundTest(ProceedingJoinPoint jp) {
    System.out.println("Before business method");
    // Call business method
    try {
        String obj = (String) jp.proceed();
        System.out.println("Return data: " + obj);
        // On success
        return obj;
    } catch (Throwable e) {
        // On failures
        e.printStackTrace();
    }
    System.out.println("After business method");
    return null;
}

Since the return value is going through @Around therefore the return value can be modified.

try {
    String obj = (String) jp.proceed();
    obj = obj + " - Modified data";
    System.out.println("Return data: " + obj);
    // on success
} catch (Throwable e) {
    // on failures
    e.printStackTrace();
}

We can even pass some parameters to the proceeding method, but that method should accept the parameter.

try {
    Employee[] empArr = new Employee[1];
    Employee dummyEmp = new Employee();
    dummyEmp.setName("Dumy");
    empArr[0]=dummyEmp;
    String s = (String) jp.proceed(empArr);
}

We can even call the method for multiple times:-

// call business method
try {
    String obj = (String) jp.proceed();
    jp.proceed();
    jp.proceed();
    System.out.println("Return data: " + obj);
    // on success
} catch (Throwable e) {
    // on failures
    e.printStackTrace();
}

Conditional Execution of Advice

Requirement:- If the business method contains @MyTx annotation then only call the advice. This is the most regularly used concept.

Let us first create an annotation:-

package com.example.demo.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTx {    
}

Business Class:-

package com.example.demo;

import org.springframework.stereotype.Component;

import com.example.demo.anno.MyTx;

// business class
@Component
public class EmployeeDao {

    @MyTx
    public String saveEmployee() {
        System.out.println("EmployeeDao.saveEmployee()");
        return "Hello";
    }
}

In Advice while declaring pointcut use @annotation.

package com.example.demo;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TxnService {

    @Pointcut("@annotation(com.example.demo.anno.MyTx)")
    public void p1() {
    }

    @Before("p1()")
    public void beforeTxn() {
        System.out.println("Before Txn");
    }

}

Output:-

Spring Boot AOP Interview Questions

  • Aspect Oriented Programming (AOP) is a programming paradigm aiming to segregate cross-cutting functionalities, such as logging/caching, from business logic in an application.
  • Spring boot application is mainly divided into three layers:
    • Web Layer for exposing the services using RESTful web services (Controller).
    • Business layer to handle business logic.
    • Data Layer for data persistence logic.
  • Each layer has a different responsibility and some common aspects apply to all layers. e.g. Logging, Security, Validation, etc. Common aspects are also called cross-cutting concerns.
  • Spring AOP has interceptors that can intercept applications and their methods.
  • Aspect:- Aspect is a class in which we define Pointcuts and Advices.
  • Advice:- It’s the behavior that addresses system-wide concerns (logging, security checks, etc…). This behavior is represented by a method to be executed at a JoinPoint. This behavior can be executed Before, After, or Around the JoinPoint according to the Advice type.
  • Pointcut:- A Pointcut is an expression that defines at what JoinPoints a given Advice should be applied.
  • JoinPoint:- Simply put, a JoinPoint is a point in the execution flow of a method where an Aspect (new behavior) can be plugged in.
  1. @Before: Advice that executes before a join point, but which cannot prevent execution flow from proceeding to the join point (unless it throws an exception).
  2. @AfterReturning: Advice to be executed after a join point completes normally.
  3. @AfterThrowing: Advice to be executed if a method exists by throwing an exception.
  4. @After: Advice to be executed regardless of how a join point exits (normal or exceptional return).
  5. @Around:
    • Advice that surrounds a join point such as a method invocation. 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. 
    • The proceed method may also be called passing in an Object[ ] – the values in the array will be used as the arguments to the method execution when it proceeds. 
    • The value returned by the around advice will be the return value seen by the caller of the method. 
    • A simple caching aspect for example could return a value from a cache if it has one and invoke proceed() if it does not. 
    • Note that proceed may be invoked once, many times, or not at all within the body of the around advice, all of these are quite legal.

In my project, I leveraged Spring AOP concepts while implementing global exception handling using @ControllerAdvice.

The @ControllerAdvice annotation provides a centralized mechanism to handle exceptions and add global data across all controllers in a Spring application. This approach aligns with the principles of Aspect-Oriented Programming (AOP), where cross-cutting concerns such as exception handling are separated into reusable components (aspects).

By using @ControllerAdvice, I created an aspect that handles exceptions, thus avoiding the need to write repetitive exception-handling code in each controller. Below is a sample implementation:

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex, WebRequest request) {
        return ResponseEntity.status(500).body("An error occurred: " + ex.getMessage());
    }
}

Benefits

  • Centralized Exception Handling: All exception-handling logic is centralized in one class, making it easier to manage and update.
  • Reuse and Maintainability: This approach enhances code reuse and maintainability by avoiding repetitive code across multiple controllers.
  • Alignment with AOP: By separating the exception-handling concern into an aspect (@ControllerAdvice), it aligns well with AOP principles, promoting modularity and separation of concerns.

In summary, @ControllerAdvice effectively creates an aspect for handling exceptions, demonstrating a practical use of AOP concepts in Spring.

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published. Required fields are marked *