Learn Aspect Oriented Programming by Example
Tags: java, aop, springboot, programming
Published: 2021-02-01 12:04:33

This post offers a great way for you to learn [Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) by studying **concrete examples**. In particular, I will showcase **SpringBoot AOP** by implementing **4 Aspects**.

**Table of Contents**:
* [**What is an Aspect?**](#overview)
* [**`@Cacheable`**](#cacheable): a standard Spring Advice
* [**Log REST calls**](#log-method) (with a custom Aspect)
* [**Performance monitoring**](#performance-monitoring) (with AOP)
* [**Retry mechanism**](#retry-operations) (with AOP)

If you’re the person who wants to skip lengthy descriptions and just look at concrete code, I’ve got you covered:

{% github pmgysel/aop-examples no-readme %}

# <a name="overview"></a> What is an Aspect?

So there are some great resources out there for an overview of Spring AOP, including this [Baeldung article](https://www.baeldung.com/spring-aop) and the [official Spring AOP documentation](https://docs.spring.io/spring-framework/docs/5.0.0.RELEASE/spring-framework-reference/core.html#aop). But since we don’t wanna focus on boring theory and rather keep things practical, here’s a really short summary how AOP works:

![Aspect oriented programming explained](https://dev-to-uploads.s3.amazonaws.com/i/xafk877crjt6kflq9yt1.PNG)

We’ll need the following terms in this tutorial:
* **Advice**: the method which implements some common task like logging or caching
* **Pointcut**: a pattern expression which matches the places where your Advice should be invoked
* **Aspect**: The Advice plus the Pointcut expression
* **Bonus - Join point**: All places in your code that represent candidates for a Pointcut

# <a name="cacheable"></a> `@Cacheable`: a standard Spring Advice

Let’s start simple and consider an already implemented Advice by [Spring](https://spring.io/), namely the [@Cacheable annotation](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html). Say your web service must compute numbers of the [Fibonacci](https://en.wikipedia.org/wiki/Fibonacci_number) series.

> If you don’t know what the Fibonacci series is: it’s the series starting with 0 and 1 and each consecutive number is the sum of the previous two numbers.

We implement the Fibonacci computation in a [@Service](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Service.html) class:

```java
@Service
public class FibonacciService {
  public Long nthFibonacciTerm(Long n) {
    if (n == 1 || n == 0) {
      return n;
    }
    return nthFibonacciTerm(n-1) + nthFibonacciTerm(n-2);
  }
}
```

Next, we use this service class in our [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) controller:

```java
@RestController
public class WebController {
  @Autowired private final FibonacciService fibonacciService;
  
  @GetMapping(path = "/api/fibonacci/{number}")
  public Long fibonacci(@PathVariable(value = "number") Long number) {
    return fibonacciService.nthFibonacciTerm(number);
  }
}
```

Our implementation is [recursive](https://en.wikipedia.org/wiki/Recursion_(computer_science)) and thus rather slow. So how do you make your web service faster? One way would be to use a [faster algorithm](https://www.baeldung.com/java-fibonacci#3-binets-formula), but let’s solve the problem with Spring’s `@Cacheable` feature. This annotation creates a cache in the background where all previous results get stored. All we must do, is add the `@Cacheable` annotation to our method:

```java
@Cacheable("Fibonacci")
@GetMapping(path = "/api/fibonacci/{number}")
public Long fibonacci(@PathVariable(value = "number") Long number) { ... }
```

Now we’re ready to test our caching mechanism by firing a REST request to `http://localhost:8080/api/fibonacci/40`. I tried to compute the 40th Fibonacci on my own laptop and here are the results:
* First REST call: 1902ms
* Second REST call: 1ms

Pretty good result eyyy🤙😎

One last thing I’d like to mention: in order to activate Spring’s cacheable feature, you have to add `@EnableCaching` to a `@Configuration` class.

# <a name="log-method"></a> Log REST calls with a custom Aspect

That was pretty easy right? So let’s move on to a more advanced use case: now we create a custom Aspect!

Our goal is to **create a log message every time some REST method gets called**. Since we might wanna add this functionality to future REST methods too, we want to generalize this task in an **Aspect**:

```java
@Before("@annotation(com.example.aop.LogMethodName)")
public void logMethodName(JoinPoint joinPoint) {
  String method = joinPoint.getSignature().getName();
  String params = Arrays.toString(joinPoint.getArgs());
  System.out.println("Method [" + method + "] gets called with parameters " + params);
}
```

The first line defines the [Pointcut expression](https://docs.spring.io/spring-framework/docs/5.0.0.RELEASE/spring-framework-reference/core.html#aop-pointcuts), and the subsequent method represents the [Advice](https://docs.spring.io/spring-framework/docs/5.0.0.RELEASE/spring-framework-reference/core.html#aop-advice). Let’s break the two down one by one:

**Pointcut**:
The Pointcut expression defines the places where our Advice is inserted to. In our case, the Aspect is applied **before** every method with a `@LogMehtodName` annotation. Note that `@LogMethodName` is our **custom annotation** which we use as Pointcut marker.

**Advice**:
The advice method is the piece of logic that generalizes a task common to many different objects. In our case, the Advice finds the originating method’s name as well as its calling parameters and logs them to the console.

With our Aspect in place, there are three additional code lines required to get everything working:
* First, add the marker `@LogMethodName` to our `fibonacci()` method
* Second, we have to add `@Aspect` to the class containing our Aspect
* Third, enable Spring’s Aspect scanning with `@EnableAspectJAutoProxy` in any `@Configuration` class

That’s it, we’ve implemented our own Advice!🙌 Let’s run a test! We fire a REST request to the web service to compute the 40th Fibonacci number and have a look at the console output:

```javascript
Method [fibonacci] gets called with parameters [40]
```

It goes without saying that such log messages will be of great help if you ever must track down bugs in your application.

# <a name="performance-monitoring"></a> Performance monitoring with AOP

In the previous example, we used a Pointcut expression of type [@Before](https://docs.spring.io/spring-framework/docs/5.0.0.RELEASE/spring-framework-reference/core.html#aop-advice-before) - here, the Advice runs before the actual method. Let’s switch gears and implement an [@Around](https://docs.spring.io/spring-framework/docs/5.0.0.RELEASE/spring-framework-reference/core.html#aop-ataspectj-around-advice) Pointcut. Such an Advice runs partly before the target method and partly after it.

Our goal now is to **monitor the execution time of any REST call**. Let’s go ahead and implement the monitoring requirement in a generalized fashion, namely an Aspect:

```java
@Around("@annotation(com.example.aop.MonitorTime)")
public Object monitorTime(ProceedingJoinPoint joinPoint) throws Throwable {
  long startTime = System.currentTimeMillis();
  Object proceed = joinPoint.proceed();
  long duration = System.currentTimeMillis() - startTime;
  System.out.println("Execution took [" + duration + "ms]");
  return proceed;
}
```

**Pointcut**:
Like before, we create a new custom annotation `@MonitorTime` for marking our Pointcuts.

**Advice**:
An `@Around` Aspect should have an argument of type [ProceedingJoinPoint](https://javadoc.io/doc/org.aspectj/aspectjweaver/latest/org/aspectj/lang/ProceedingJoinPoint.html). This type has a `proceed()` method which triggers the execution of the actual target method. So in our Advice, we first query the current time in milliseconds. After the target method is executed, we measure the current time again, and from there we can compute time difference.

Let’s go ahead and mark our target method with the `@MonitorTime` annotation:

```java
@MonitorTime
@LogMethodName
@Cacheable("Fibonacci")
@GetMapping(path = "/api/fibonacci/{number}")
public Long fibonacci(@PathVariable(value = "number") Long number) { ... }
```

By now, our REST method has quite some Pointcut markers attached to it😉 Anyways, let’s go ahead and test our performance monitoring feature. As before, we compute the 40th Fibonacci number:

```javascript
Method [fibonacci] gets called with parameters [40]
Execution took [1902ms]
```

As you can see, this particular REST call took 1902ms. With this `@Around` Aspect in place, you’re definitely an advanced AOP programmer!💪

# <a name="retry-operations"></a> Retry mechanism with AOP

Distributed systems can experience concurrency issues. One such example would be when two web service instances are simultaneously trying to access the same record in a database. Oftentimes, such a lock problem can be resolved by retrying the operation. The only requirement here is that the operation is idempotent.

Let’s go ahead and create an Aspect which **transparently retries an operation until it succeeds**:

```java
@Around("@annotation(com.example.aop.RetryOperation)")
public Object doIdempotentOperation(ProceedingJoinPoint joinPoint) throws Throwable {
  int numAttempts = 0;
  RuntimeException exception;
  do {
    try {
      return joinPoint.proceed();
    } catch(RuntimeException e) {
      numAttempts++;
      exception = e;
    }
  } while(numAttempts < 100);
  throw exception;
}
```

**Pointcut**:
Our Advice runs around any method with the custom annotation `@RetryOperation`.

**Advice**:
In the `try` statement, we run the target method. This method might throw a `RuntimeException`. If this happens, we increment the `numAttempts` counter and simply rerun the target method. As soon as the target method succeeds, we exit the Advice.

For demonstration purposes, let’s create a REST method for storing a String. This method will fail 50% of the time:

```java
@RetryOperation
@LogMethodName
@PostMapping(path = "/api/storeData")
public void storeData(@RequestParam(value = "data") String data) {
  if (new Random().nextBoolean()) {
    throw new RuntimeException();
  } else {
    System.out.println("Pretend everything went fine");
  }
}
```

Thanks to our `@RetryOperation` annotation, the above method will be retried until it succeeds. Moreover, we use our `@LogMethodName` annotation so we can see every method invocation. Let’s go ahead and test our new REST endpoint; for this purpose we fire a REST request to `localhost:8080/api/storeData?data=hello-world`.

```javascript
Method [storeData] gets called with parameters [hello-world]
Method [storeData] gets called with parameters [hello-world]
Method [storeData] gets called with parameters [hello-world]
Pretend everything went fine
```

In the above case, the operation failed 2 times and only succeeded on the third try.

# Conclusion

Congrats, you’re a professional AOP programmer now🥳🚀 You can find a fully working web service with all Aspects on my Github repo:

{% github pmgysel/aop-examples no-readme %}

Thanks so much for reading, please leave a comment if you have any questions or feedback!