Spring AOP Custom Annotation Example

This is a practical demonstration of using Spring AOP Custom Annotations. Write your own custom annotation in a spring or a spring boot application and apply advices on the annotated targets.

Overview

Spring AOP is a flexible and light-weight implementation of Aspect Oriented Programming. One of the good things about Spring AOP is that it is easy and it works well with the Spring Dependency Injection (DI), and Inversion of Control (IoC). The Aspects and advices helps to apply additional behaviour to existing classes so that the classes remain unchanged and focused.

In this tutorial we will learn how to create a Custom annotation to apply advice on selected target methods only.

What is Custom Annotation based Advice?

In this section we will learn what are the Custom Annotation Based advices and understand their benefits. We will also look at the steps involved in implementing Custom Spring AOP Annotations.

To Summarise quickly, an aspect provides advices and Pointcut expressions, and it applies the advices on the target methods that match with the Pointcut expression.

Benefits of Spring AOP Custom Annotations

Generally, the Pointcuts can use wildcard expressions to match target methods with their name, signature, or the class or package they belong to. However, in such cases the target method doesn’t have any control wether it wants to use the particular advice.

For example, next is a Pointcut expression that is applied on all the readFile methods from FileSystemStorageService class from the specified package.

"execution(* com.amitph.spring.aop.service.FileSystemStorageService.readFile(..))"Code language: Java (java)

On the other hand, having annotation based Spring AOP Advices, the target methods can decide if they want the advice. For example, when a Pointcut expression is based on an annotation type.

"@annotation(CustomAnnotation)"Code language: Java (java)

With this expression, the respective advice will run only for the methods, which use this annotation.

Next are the high level benefits of Custom Annotation based advices

  • More Flexibility: Referring to the above example, if one of the readFile methods of the class wants to opt out of the advices, we need to go and change the aspect Pointcut expression. Or, we need to change the method name. This can be done easily with Annotation based advices.
  • Target method gets more control: Any target method can simply remove or add the annotation without affecting any other parts of the code.
  • Advices can be reused: The annotation based advices are not bound to a package, a class or the name of a target method. Thus, we can reuse them as part of external libraries.
  • Improved Readability: Methods with custom annotation upon them helps in improved readability and documentation. For example, Spring’s declarative transaction management, which is an example of Custom Spring AOP annotations. When we see a method with @Transactional annotation we know that this method executes as part of a transaction.

Implementation Steps

Next, are the two high level steps required to implement Custom Annotations based Spring AOP.

  • Create a Custom Annotation: First, we need to create a custom annotation. As a good practice an annotation name should reflect the intent of the advice. For example, Spring’s @Transnactional annotation reflects the intent correctly.

    Alternatively, we can reuse any existing annotation based on the purpose. For example, we want to apply an advice on all methods in the application service layer. In Spring we use @Service annotation for the classes in Service layer and we can define our Pointcut expression based on that.

    Having this expression, the advice will run for all methods of @Service classes.
"@target(org.springframework.stereotype.Service)"Code language: Java (java)
  • Once the custom annotation is created and applied, we need to define a pointcut expression for the annotation, and chose a correct type of Advice.

Initial Setup

Before we write our first custom annotation example, let’s get a few initial steps done.

Dependency

To use Spring AOP, we need to add a dependency on spring-aspects project.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>{spring.aop.version}</version>
</dependency>Code language: HTML, XML (xml)

Enable AspectJ Proxies

Once the dependency is set, we need to enable AspectJ proxies in a Spring Application. To do that we need to apply @EnableAspectJAutoProxy annotation on a Application Configuration class.

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfig {
    ....Code language: Java (java)

The Spring Boot auto configuration automatically enables AspectJ proxies when it discovers the dependency on the class path. Thus, this step is not mandatory for a Spring Boot application.

Create Custom Annotation

Let’s create a Java custom annotation, which will then be used by the target methods.

For this tutorial we will create a Spring AOP based common exception handler for an application. We will use Spring AOP AfterThrowing advice to catch and categorise different exceptions thrown by the target methods.

First, we will create a custom annotation ExceptionsHandled.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExceptionsHandled {
}Code language: Java (java)

Note the target of this annotation is of Method type. Thus it can be applied on the method level.

Create Advice and Pointcut

Now that the custom annotation is created, let’s write an advice and a Pointcut expression.

Next is an example of Custom annotation based advice. Although we are using Spring AOP @AfterThrowing advice here the same Pointcut expression will work with other types of advices as well.

@Slf4j
@Aspect
@Component
public class ExceptionsAspect {
    @AfterThrowing(value = "@annotation(ExceptionsHandled)", throwing = "e")
    public void exceptionsHandler(JoinPoint joinPoint, Exception e) {
        log.error("Exception in method: {}", joinPoint.getSignature().getName());
        Task task = (Task) joinPoint.getArgs()[0];

        if (e instanceof InvalidInputException) {
            log.info("File not found, bad request");
            task.setStatus(TaskStatus.NOT_FOUND);
        } else if (e instanceof TaskProcessException) {
            if (e.getCause() instanceof ResourceAccessException)
                log.info("Temporary error, please retry");
            task.setStatus(TaskStatus.RETRY);
        }
    }
}Code language: Java (java)

To highlight a few things here, note that we are using @annotation expression to denote the custom annotation name. Also, we are specifying the name of exception argument. Inside the advice we receive the actual exception as an argument, on which advice can take action.

Remember that if JoinPoint is the first argument to advice, we do not need to specify it in arguments expression.

Use Spring AOP Custom Annotation

So far, we have enabled Spring AOP in a Spring or Spring Boot project, created a custom annotation and set up an advice based on the custom annotation. Now, we will use the custom annotation on a target method.

Next is an example of using Custom Spring AOP Annotation.

@Slf4j
@Service
public class FileSystemStorageService extends StorageService{

    @ExceptionsHandled
    public List<String> readFile(Task task) throws IOException {
        log.info("Reading file name: {}", task.getFileName());
        Path filePath = Path.of(getBaseDir() + task.getFileName());

        if (Files.notExists(filePath)) {
            throw new InvalidInputException("File doesn't exist, filename: " + task.getFileName());
        }

        return Files.readAllLines(filePath);
    }
}Code language: Java (java)

This is one of the regular service method which performs a task. We have simply applied the @ExceptionsHandled annotation on it, without modifying any of its implementations.

Let’s run the application and execute the readFile method and expect it to throw an exception.

INFO  | [main] c.a.s.a.s.FileSystemStorageService:17 - Reading file name: test.txt
ERROR | [main] c.a.s.a.s.ExceptionsAspect:16 - Exception in method: readFile
INFO  | [main] c.a.s.a.s.ExceptionsAspect:20 - File not found, bad requestCode language: plaintext (plaintext)

The target method correctly throws InvalidInputException when the provided file is not found. As per the logs, the advice is executed after the exception and it is able to categorise the type of exception.

Summary

This tutorial gave a detailed overview of Spring AOP Custom annotations. Using the custom annotations based Pointcuts and advices, we can enforce additional functionalities on the target methods. We first understood what are Custom Spring AOP annotations, and how they benefit in terms of flexibility. Then we followed a step by step approach to implement a practical Spring or Spring Boot application that uses Spring AOP Custom Annotation based advices. For the purpose of example we created a Spring AOP based common exception handler advice for different classes.

For more on Spring and Spring Boot, please visit Spring Tutorials.