Aspect Oriented Programming for Babies
Spring AOP illustrated
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:
- 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. - 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 declarevoid
as its return type. We also setannotationEventMonitor()
as the pointcut. - The
monitorAfter()
method has declared an after-returning advice using the@AfterReturning
annotation. The method should declarevoid
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 declareObject
as its return type, and the first parameter of the method must be of typeProceedingJoinPoint
. Invokingproceed()
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.