Aspect Oriented Programming for Babies

Spring AOP illustrated

Jonathan Manera
5 min readJul 20, 2023

Aspect Oriented Programming (AOP) is a powerful tool that enables a clean modularization of “crosscutting” concerns of enterprise applications; such as error handling, transaction management, monitoring and logging, just to name a few.

As part of this tutorial, we’ll explore Spring AOP and see how it works.

Aspect-Oriented Programming Illustrated

AOP terminology can be confusing, so the following slides illustrate the terminology in a graphic way to make it easier to understand.

How Spring AOP Works?

With a general understanding of Aspect Oriented Programming in hand, let’s put the theory into practice.

Maven Dependencies

First, let’s add the Spring Boot Starter AOP dependency to the pom.xml file.

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

Defining the Join Point

The Join Point in this example will be determined by a custom Java annotation.

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

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(METHOD)
@Retention(RUNTIME)
public @interface EventMonitor {
}

We have specified the ElementType.METHOD property for the @Target annotation, which means the annotation only works on methods. As for @Retention, it indicates that the annotation is available at runtime according to RetentionPolicy.RUNTIME.

Below is an example that adds @EventMonitor as the join point for the execution of process().

@Component
public class MyEventProcessor {

@EventMonitor
public void process(MyEvent event) throws InterruptedException {
// doing something that takes time...
Thread.sleep(1000);
}
}

Creating the Aspect

With our annotation in place, let’s start by declaring the Aspect. In order to accomplish this, we create a class annotated with @Aspect and @Component.

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

@Aspect
@Component
public class EventMonitorAspect {
}

This class does not have any logic yet, but here we will implement our Pointcuts and Advices.

Declaring Pointcuts

The key concept behind AOP lies in Join Points matched by Pointcuts.

Spring AOP only supports join points for method execution, so a pointcut matches the execution of methods on Spring beans.

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class EventMonitorAspect {

@Pointcut("@annotation(EventMonitor)")
private void annotationEventMonitor() {
}

}

The pointcut declaration has two parts:

  1. The signature comprising a name and any parameters. In this case, the method serving as the pointcut signature has no parameters and has a void return type.
  2. The pointcut expression that determines which method executions we are interested in. Here, the pointcut expression that forms the value of the @Pointcut annotation is the @annotation Pointcut Designator keyword.

Spring AOP supports the following Pointcut Designator (PCD) for use in pointcut expressions:

  • execution: For matching method execution join points.
  • within: Limits matching to join points within certain types.
  • this: Limits matching to join points where the bean reference is an instance of the given type.
  • target: Limits matching to join points where the target object is an instance of the given type.
  • args: Limits matching to join points where the arguments are instances of the given types.
  • @target: Limits matching to join points where the class of the executing object has an annotation of the given type.
  • @args: Limits matching to join points where the runtime type of the actual arguments passed have annotations of the given types.
  • @within: Limits matching to join points within types that have the given annotation.
  • @annotation: Limits matching to join points where the subject of the join point has the given annotation.

Learn more about pointcuts here.

Declaring Advice

Advice is associated with a pointcut expression and can take place before, after, or around method executions matched by the pointcut.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.time.Instant;

@Aspect
@Component
public class EventMonitorAspect {

@Pointcut("@annotation(EventMonitor)")
private void annotationEventMonitor() {
}

@Before("annotationEventMonitor()")
public void monitorBefore() {
long start = System.currentTimeMillis();
System.out.println("Processing started at " + Instant.ofEpochMilli(start) + "ms");
}

@Around("annotationEventMonitor()")
public Object monitorAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object arg0 = joinPoint.getArgs()[0];
System.out.println("Processing event: " + arg0);
return joinPoint.proceed();
}

@After("annotationEventMonitor()")
public void monitorAfter() {
long end = System.currentTimeMillis();
System.out.println("Processing ended at " + Instant.ofEpochMilli(end) + "ms");
}
}

Here:

  • The monitorBefore() method has declared a before advice in the aspect using the @Before annotation. The method should declare void as its return type. We also set annotationEventMonitor() as the pointcut.
  • The monitorAfter() method has declared an after-returning advice using the @AfterReturning annotation. The method should declare void as its return type. This advice runs when a matched method execution returns normally.
  • Finally, the aroundAfter() method runs “around” the matched method’s execution and can work both before and after. The method should declare Object as its return type, and the first parameter of the method must be of type ProceedingJoinPoint. Invoking proceed() without arguments results in supplying the underlying method with the caller’s original arguments.

Learn more about advice here.

Testing the Implementation

Now, let’s create an integration test to verify our implementation.

@SpringBootTest
class MyEventProcessorTest {

@Autowired
MyEventProcessor processor;

@Test
void process_test() throws InterruptedException {
processor.process(
new MyEvent(UUID.randomUUID(), "Example")
);
assert true;
}
}

In the code snippet above, SpringBootTest is used to make a simple integration test.

Let's run mvn verify in the terminal, and check the results.

The log shows that once the method annotated with @EventMonitor is executed, additional logic in the aspect is added to the object.

Thanks for reading. I hope this was helpful!

The example code is available on GitHub.

--

--

Jonathan Manera

If you wish to make a Java app from scratch, you must first invent the universe.