Aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. In simpler terms, instead of adding the code for logging into the class method itself, we will separate it. Think of it like the layers of an onion.

Link to Part 6
Part 8 Now Available : Here

Table of Contents:

  1. Introduction
  2. Aspect-Oriented Programming (AOP)
  3. Why AOP Over Inheritance?
  4. Understanding AOP Pitfalls
  5. Getting Started with AOP in Spring Boot
  6. Terminology Used in AOP
  7. Coding a Simple App
  8. Adding AOP to Our Users App
  9. Pointcuts in More Detail
  10. Conclusion

Pre-requisites:

Good grasp of Spring and Spring Boot basics

What is an Aspect?

An aspect is just a particular behaviour that needs to be present in all layers of your application. For example, a typical web application might have a web, business and data layers. Logging needs to be present for all these layers.

Aspect-oriented programming (AOP):

Let’s say you’re building a web application that manages user authentication and authorization. You have various methods across different classes responsible for handling different aspects of user management, such as login, logout, user registration, etc. Now, you want to log every method call along with its arguments and return value for auditing purposes. Instead of adding logging code to each of these methods, you can utilize AOP to achieve this in a more modular and reusable way.

Think of it like a proxy inside your application, that can intercept all method calls happening.

Why is this better than inheritance?

Before we proceed any further, it is really important to ask this question. “why do we need to separate logging out in the first place?”. Well think about this. Imagine if you had thousands of classes and all the classes needed to have the same log message format. That would be pretty messy to add a code like this to every method in the class.

System.out.println("Method A of Class B was called");

You might be thinking, Ok, “we could move this logging functionality into a separate class and inherit that class where ever we need logging. right?

Yes, that would be a solution. But the same problem still remains. What happens if one day you decide to add a new method to the logger class and have some of our other classes use it? You will have to go and change the method call in every class that inherits it and this would be tedious.

With AOP, you don’t need to make changes to any other classes ever. In fact, you won’t even have to call the logger method anywhere. It will be all taken care by Spring.

Look before you leap:

AOP can be one of the most confusing concepts in Spring Boot if you are getting started. The importance of AOP only comes to light when you are working on huge projects that deal with a web of hundreds and thousands of classes and is not necessary to use it for small projects.

It is also important to note the performance hit this can bring to your application. Since this AOP code needs to be weaved into your other code at runtime, there will be some latency in microseconds.

How do you do AOP in Spring Boot?

There are two popular libraries in Spring Boot to do AOP. AspectJ and Spring-AOP. AspectJ is the original module released in 2001 and is more powerful but Spring-AOP is simpler to understand and is sufficient for most use cases.

There are many differences between the two. AspectJ is really comprehensive and provides a lot of tools for aspect-oriented programming. Spring-AOP is like a cut down version of AspectJ and has much smaller list of features.

One of the key difference is AspectJ supports weaving at runtime, compile time and load time while Spring-AOP only supports weaving at runtime. So Spring-AOP can be a bit slower.

None the less, we will be using Spring-AOP here as it is easier to get started.

Terminology used in AOP:

  1. Aspect: An aspect is a modular unit of cross-cutting concern implementation. It encapsulates behaviors or concerns that cut across multiple parts of an application. Aspects are Java classes annotated with @Aspect and contain methods to be executed.
  2. Pointcut: Pointcuts are expressions that determine which method calls will be intercepted by the aspect. For example, only print logs for methods of Class A.
  3. Advice: Advice represents the behavior that you want to apply at a particular join point selected by a pointcut. There are different types of advice, including @Before (executed before the join point), @AfterReturning (executed after the join point completes normally), @AfterThrowing (executed if the join point throws an exception), @After (executed after the join point, regardless of its outcome), and @Around (surrounds the join point).
  4. Joinpoint: A joinpoint is a specific point in the execution of the program where an aspect can be applied. It includes method invocations, method executions, object instantiations, field accesses, etc. All method calls that are intercepted by an aspect are considered joinpoints.
  5. Weaving: Weaving is the process of integrating aspects with the rest of the application. It links an aspect with objects in the application to create an advised object. Weaving can be done at compile time, load time, or runtime. The aspect is invoked at the right moment during program execution based on the specified pointcuts.
  6. Weaver: The framework responsible for ensuring that an aspect is invoked at the right time is called a weaver. In Java AOP frameworks like AspectJ or Spring AOP, the weaver is responsible for integrating aspects with the application code and executing them at the specified joinpoints.

Don’t worry if these terms don’t make much sense now. You will get a much better idea once you see the code.

Coding Set-Up:

Go ahead to Spring Initializr and download your starter zip code.

Once downloaded, extract and open it in your favourite IDE or editor. I will use VS Code. The first step is adding Spring-AOP as a dependency.

Open the pom.xml file and add the following dependency.

  <!-- Spring AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.14</version>
</dependency>

That it. Now we can get started with writing the actual code.

Coding a simple app:

Let’s start by creating a simple User API and then add AOP to it later. If you already have an app running, skip ahead to the ‘Adding AOP’ section.

Let’s create the user model first.

// User.java
public class User {
private Long id;
private String username;
private String email;

// Getters and setters
}

It’s a basic user model with id, name and email. Also implement the Getters and Setters for each field or use something like lombok.

Next, let’s create the user repository interface.

// UserRepository.java
import java.util.List;

public interface UserRepository {
User findById(Long id);
List<User> findAll();
User save(User user);
void deleteById(Long id);
}

It just defines a few methods to get user, save user and delete user.

Now, let’s implement the actual repository using this interface.

// InMemoryUserRepository.java
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class InMemoryUserRepository implements UserRepository {
private Map<Long, User> users = new HashMap<>();
private Long idSequence = 1L;

@Override
public User findById(Long id) {
return users.get(id);
}

@Override
public List<User> findAll() {
return new ArrayList<>(users.values());
}

@Override
public User save(User user) {
if (user.getId() == null) {
user.setId(idSequence++);
}
users.put(user.getId(), user);
return user;
}

@Override
public void deleteById(Long id) {
users.remove(id);
}
}

And finally let’s create the REST controller to access these methods.

// UserController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository userRepository;

@GetMapping
public List<User> getAllUsers() {
return userRepository.findAll();
}

@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userRepository.findById(id);
}

@PostMapping
public User createUser(@RequestBody User user) {
return userRepository.save(user);
}

@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
}
}

Adding AOP to our users app:

Now let’s add some logging to our code using AOP. Create a file called LoggingAspect.java and add the following code.

// LoggingAspect.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.logging.Logger;

@Aspect
@Component
public class MethodExecutionAspect {
private Logger logger = Logger.getLogger(getClass().getName());

@Before("execution(* UserController.*(..))")
public void beforeControllerMethodExecution(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
logger.info("Before executing UserController method: " + methodName);
}
}

Here we import necessary classes and annotations. The Logger class is used to log information to the console.

This is a Java class called MethodExecutionAspect. It's annotated with @Aspect, which tells Spring that this class contains code related to Aspect-Oriented Programming (AOP), specifically for method execution.

private Logger logger = Logger.getLogger(getClass().getName());

Here, we’re creating an instance of the Logger class to log messages.

@Before("execution(* UserController.*(..))")
public void beforeControllerMethodExecution(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
logger.info("Before executing UserController method: " + methodName);
}

This is a method beforeControllerMethodExecution that gets executed before any method of the UserController class is called.

The @Before annotation indicates that this method should run before the advised method (UserController methods in this case). Inside this method, we're obtaining the name of the method being executed (UserController method) and logging a message indicating that we are about to execute it. The @Before is the advice here. They indicate when the code should execute.

Whatever is inside the Before, which is “execution(* UserController.*(..))” is called the Pointcut. They indicate if the code should execute. It is basically a regex that tells when this method should execute. We will explore more about pointcuts in the next section.

But for now, if you run your code using

mvn spring-boot:run

and visit one of the routes example: http://localhost:8080/users/ you will see the log message being printed by the AOP code.

Pointcuts in more detail:

Pointcut is the language used to tell Spring Boot for what classes and methods the aspect code should be executed.

Syntax of Pointcuts:

Pointcuts are defined using a specific syntax to match method signatures, annotations, or other criteria. The syntax commonly used in Spring AOP includes the following elements:

1. Method Signature:

  • Method Name: Matches methods with a specific name.
  • Return Type: Matches methods with a specific return type.
  • Parameter Types: Matches methods with specific parameter types.

Example:

execution(public void com.example.service.UserService.*(..))

This pointcut matches all public methods (public) with void return type (void) in the com.example.service.UserService class.

2. Wildcards:

  • *: Matches any sequence of characters in a method name or package name.
  • ..: Matches any number of parameters of any type.

Example:

execution(* com.example..*.*(..))

This pointcut matches all methods in any class within the com.example package and its sub-packages.

3. Modifiers:

  • public: Matches public methods.
  • protected: Matches protected methods.
  • private: Matches private methods.
  • static: Matches static methods.

Example:

execution(public * com.example.service.UserService.*(..))

This pointcut matches all public methods in the com.example.service.UserService class.

4. Annotations:

  • @annotation: Matches methods annotated with a specific annotation.
  • @within: Matches methods within types that are annotated with a specific annotation.
  • @target: Matches methods that have an annotation of a specific type in their target.
  • @args: Matches methods with parameters that are annotated with a specific annotation.

Example:

@annotation(org.springframework.web.bind.annotation.GetMapping)

This pointcut matches all methods annotated with @GetMapping from the Spring Web framework.

5. Combining Pointcuts:

  • && (AND): Combines multiple pointcut expressions.
  • || (OR): Matches if either of the pointcut expressions matches.
  • ! (NOT): Negates a pointcut expression.

Example:

execution(* com.example.service.*.*(..)) && !execution(void set*(..))

This pointcut matches all methods in the com.example.service package except setter methods (methods starting with set).

The @Pointcut annotation:

The @Pointcut annotation in Spring-AOP is used to define a reusable pointcut expression. It allows developers to abstract and name complex pointcut expressions, making them easier to manage and reuse across multiple advice methods within an aspect.

The @Pointcut annotation is applied to a method within an aspect class, and the method typically does not have a body. It specifies a pointcut expression using AspectJ's pointcut syntax.

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

@Pointcut allows developers to abstract complex pointcut expressions into named methods, making them easier to understand and reuse across multiple advice methods within an aspect. This promotes code reusability and reduces duplication.

Instead of scattering pointcut expressions throughout the aspect class, @Pointcut allows developers to centralize pointcut definitions in a single location within the aspect class, making it easier to manage and modify them as needed.

Example use case:

@Aspect
public class LoggingAspect {

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

@Before("serviceMethods()")
public void logBeforeServiceMethods(JoinPoint joinPoint) {
// Logging logic before service method execution
}

@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturningServiceMethods(JoinPoint joinPoint, Object result) {
// Logging logic after successful service method execution
}
}

In this example, the serviceMethods() pointcut defined using @Pointcut captures all methods in the com.example.service package. This pointcut is then reused in the logBeforeServiceMethods() and logAfterReturningServiceMethods() advice methods to apply logging behavior before and after the execution of service methods.

Of course, covering everything about point cuts would be a course on its own. You can read more about them here.

Wrapping Up:

You’ve now learned the basics of the art of weaving concerns in your applications with Spring-AOP. However, use AOP carefully, ensuring that it enhances code readability and maintainability rather than complicating it unnecessarily.

“Fate would not have the reputation it has, if it simply did what it seemed it would do.”
Amor Towles, A Gentleman in Moscow