Mastering Aspect-Oriented Programming (AOP) with Spring Framework

Everwell Engineering
Pulse by Everwell
Published in
4 min readMay 17, 2024

Aspect-Oriented Programming (AOP) is a powerful paradigm that complements Object-Oriented Programming (OOP) by enabling cross-cutting concerns such as logging, security, and transaction management to be modularised and re-usable.

In this blog post, we’ll explore how to leverage Spring Framework’s AOP support to achieve cleaner and maintainable code. We’ll cover the basic concepts of AOP, demonstrate how to define key terminologies and provide code examples to illustrate common use cases.

Understanding Aspect-Oriented Programming:

A programming paradigm that allows developers to modularize cross-cutting concerns, AOP is a set of functionalities that affect multiple parts of an application. Unlike traditional procedural or object-oriented programming, where such concerns are scattered throughout the codebase, AOP enables developers to encapsulate them into reusable modules called aspects. Aspects are applied to target components through a process called weaving, which inserts the aspect’s behavior into the target components at compile time or runtime.

Setting up AOP:

For using Spring AOP, the first step to do would be to add the dependencies which allow us to extend the functionalities. Here is how the dependency can be added for both Maven and Gradle users.

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

Property Changes (Optional):
Depending on your requirements, you might need to configure certain properties related to AOP behavior. These properties can be set in your application.properties or application.yml file.

spring.aop.proxy-target-class: By default, Spring AOP uses JDK dynamic proxies for AOP proxying. If you want to use CGLIB proxies instead, you can set this property to true.
spring.aop.auto: By default, Spring Boot automatically enables AOP if it detects the presence of AOP-related dependencies. If you need more control over AOP auto-configuration, you can set this property to false and manually configure AOP.

Here’s an example application.properties and application.yaml file with these properties configured:

spring.aop.proxy-target-class=true# Disable automatic AOP configuration
spring.aop.auto=false
spring:
aop:
proxy-target-class: true
auto: false

Implementation using AspectJ:

Lets assume that we have a use-case for adding logging support to one of the existing services outlined below.

import org.springframework.stereotype.Service;
import com.javainuse.model.Employee;
@Service
public class EmployeeService {
public Employee createEmployee(String name, String empId) {
Employee emp = new Employee();
emp.setName(name);
emp.setEmpId(empId);
return emp;
}
public void deleteEmployee(String empId) {

}
}

In case we will add specific loggers, these would need to be replicated again and again for different functions within the same and different services present in the codebase. AOP can help by encapsulating the cross-cutting concern.

First, we would enable AutoProxying from the main class.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class SpringBootHelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootHelloWorldApplication.class, args);
}
}

To make use of the AOP, we will define an Aspect which is a modular unit of cross-cutting functionality. In Spring AOP, aspects are implemented as ordinary Java classes annotated with the @Aspect annotation.

Now, we would have to intercept the execution at the specific place where logging would be required. For this, we will use a JoinPoint which is a candidate point in the Program Execution of the application where an aspect can be plugged in and a PointCut, an expression which defines the point like a predicate where the behaviour is to be applied. This point could be a method being called, an exception being thrown, or even a field being modified.

Next, we will need to define what behaviour or functionality needs to be injected at these joints. We define this by using an Advice which dictates whether the behaviour should be injected before, after or around the JointPoint.

Defining an Aspect and applying Advices through joinpoints and pointcuts:

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

@Aspect
@Component
public class EmployeeServiceAspect {

//advice method executes before the joinpoint matched by the pointcut expression.
@Before(value = "execution(* com.java.example.service.EmployeeService.*(..)) and args(name,empId)") //pointcut expression
public void beforeAdvice(JoinPoint joinPoint, String name, String empId) {
System.out.println("Before method:" + joinPoint.getSignature());
System.out.println("Creating Employee with name - " + name + " and id - " + empId);
}

//advice method executes after the joinpoint matched by the pointcut expression.
@After(value = "execution(* com.java.example.service.EmployeeService.*(..)) and args(name,empId)")//pointcut expression
public void afterAdvice(JoinPoint joinPoint, String name, String empId) {
System.out.println("After method:" + joinPoint.getSignature());
System.out.println("Successfully created Employee with name - " + name + " and id - " + empId);
}
}

Now, whenever we use the createEmployee function, the aspect would be weaved in to produce print lines as desired.

Before method: void com.java.example.service.EmployeeService.createEmployee(String, String)
Creating Employee with name - John and id - 123
After method: void com.java.example.service.EmployeeService.createEmployee(String, String)
Successfully created Employee with name - John and id - 123

However, while AOP provides decoupling of behaviours which need to cross-cut across modules improving the scalability, maintainability and reusability of the code, there is a significant overhead for the proxy creation and method invocation leading to performance drain and higher memory consumption (due to increase in object creation). Due to this, the powerful AOPs need to be used in moderation and tuned specific to use-cases. Some of the best practice tips would be to

Keep Aspects Focused: Define aspects with a single responsibility to maintain clarity and reusability.
Minimize Performance Impact: Avoid overly complex pointcuts and advice to minimize runtime overhead.

Although we have used AspectJ for the above example, you can also choose CGLIB an alternative to AspectJ for implementing AOP in your codebase. While CGLib supports runtime weaving and is easier to use, there is limited support for extension especially for final classes and methods. A rule of thumb which could be adopted would be to use AspectJ when you need fine-grained control over weaving and prefer a language extension approach.

Credits: Vaibhav Sharma

--

--