Implementing Custom Annotation in SpringBoot

Shubhankar Kotnala
7 min readMay 13, 2023

Spring Boot is a popular framework for developing Java applications. It provides a number of features that make it easy to get started with development, including auto-configuration, starter dependencies, and embedded containers.

One of the benefits of using Spring Boot is that we can use annotations to make our lives easy as a Developer, however sometimes there is a need to create custom annotation for our logic. Custom annotations can be used to add metadata to your code, which can be used by Spring Boot and other frameworks to provide additional functionality.

In this article we’ll implement a Custom Spring AOP Annotation, but first let’s understand what AOP is?

What is Spring AOP?

Spring AOP (Aspect Oriented Programming) is a powerful feature of the Spring Framework that allows you to decouple cross-cutting concerns from your business logic. Cross-cutting concerns are things like logging, security, and transaction management that are common to many different parts of your application.
To keep it simple, Spring AOP is used to add behaviour to the code without actually inserting it into the business logic, so that we can focus on our business logic, and keep the most common, reusable part of code separately.

Let’s implement Custom Annotation

Assuming we are having a fresh Spring Boot project, let’s get started with the basic setup for our application.

To create a initial project with dependencies see https://start.spring.io

Adding Dependencies

Before we being with anything, we need to add AOP dependency to our project as a prerequisite.

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

This will pull the required libraries to enable us to implement Aspect.

Creating our Custom Annotation

In this article we will focus on how we can log requests in Spring Boot using annotation. Let’s create a new @ interface

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLogger {

}

Here we are creating an annotation named RequestLogger with Target as Method since we want to apply this annotation to only Methods, and Retention with RetentionPolicy as Runtime since we want to have this annotation present during the runtime of the application.

Creating Aspect to handle Annotation

Let’s create our Aspect that would hold the logic of our annotation.
This is just a class with @ Aspect annotation.

@Aspect
@Component
@Slf4j
public class CustomAspect {
//We'll handle our annotation here
}

Notice we have also used Component annotation, this is just to make sure our class is a bean and is detected by Spring

Let’s continue to handle how our annotation will behave.

@Around("@annotation(requestLogger)")
public Object logRequest(ProceedingJoinPoint joinPoint) {
//This is where our custom logic will go
}

There are few things to observe in the above code snippet,
1. The @ Around annotation. This is the advice which we have, suggesting that the following method will be executed before and after the method execution which is annotated with RequestLogger annotation.
2. We have a single parameter ProceedingJoinPoint which in our case will have the context of the method which is currently in the scope of being executed.

Adding custom request logging logic to the annotation

Now that we have setup our new annotation, it’s time to add some code to it to log the incoming request. This is of-course not how it should be to log incoming request, but it would give an idea on how we can use annotations.

We’ll use basic Spring technique to get the current request uri and log it as soon as it is ready to be executed, and after our method is done processing the request.

@Around("@annotation(requestLogger)")
public Object logRequest(ProceedingJoinPoint joinPoint) {
//HttpServletRequest to be added in the context
log.info("{} : Request received", request.getRequestURI());
Object obj = joinPoint.proceed();
log.info("{} : Request finished", request.getRequestURI());
return obj;
}

Since in the joinPoint we have the current method context, when we say joinPoint.proceed() this is where our method that is annoted with our annotation starts it’s execution.
Now that we are ready with the annotation to be applied to our Rest Controller to have the APIs logged.

Adding test code to test our annotation

Let’s add a basic RestController with one API to test if our annotation works as expected or not.

@RestController
@RequestMapping("/v1/test")
public class TestController {
@GetMapping
public String test() {
return "Hello World";
}
}

We have created a very basic API /v1/test which will return Hello World once called. Let’s start our server to see if the API works.

Now that our API is working, let’s go ahead and add our Annotation to the controller endpoint and run the API again.

@RestController
@RequestMapping("/v1/test")
public class TestController {
@GetMapping
@RequestLogger
public String test() {
return "Hello World";
}
}

We see no difference in the response of our API, however if we look at our server logs, we do see something which our annotation added.

We can see our annotation added a log before executing the method, and after the method execution is finished. Just to make sure if this was the real behaviour, let’s add a log statement into our API endpoint method and hit the endpoint again.

As we can see the log statement is after we printed the initial request received statement and before the request finished statement. This means we are actually having this annotation around our API endpoint method.

Adding ‘parameters’ to custom annotations?

We have seen how to implement a basic custom annotation, however we have also used annotations with parameters in them. Let’s see how we can add parameters to our annotation.

For our RequestLogger annotation it would be better if we have the option to enable or disable it without having to apply or remove the annotation.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLogger {
boolean enabled() default true;
}

We have added enabled() parameter to our @ interface. Notice how these are not regular parameter but method. There are certain restriction on how we can write these methods. These methods cannot have any parameters, and cannot throw an exception. Also, the return types are restricted to primitives, String, Class, enums, annotations, and arrays of these types. Also the default values cannot be null

Updating our Aspect

Now that we have our annotation accepting parameters we can update our aspect to fetch this value and perform action accordingly.

@Around("@annotation(requestLogger)")
public Object logRequest(ProceedingJoinPoint joinPoint, RequestLogger requestLogger) throws Throwable {
if (requestLogger.enabled()) {
log.info("{} : Request received", request.getRequestURI());
Object obj = joinPoint.proceed();
log.info("{} : Request finished", request.getRequestURI());
return obj;
} else {
log.warn("{} : Request received but logging is disabled", request.getRequestURI());
return joinPoint.proceed();
}
}

We have added one bounded parameter to our method which holds the current context of the annotation, and can be used to fetch the parameter values as a normal object.

Testing the new annotation

Since we have added a default value which makes enabled as true there is no need to always add parameters to the annotation, so we can directly run our server again and hit the API, and we should see no difference yet. However if we disable the annotation, we should see some different results.

@GetMapping
@RequestLogger(enabled = false)
public String test() {
log.info("Inside API method");
return "Hello World";
}

Now if we run the server and hit the API, we see something different in our logs, which indicated that we did read the parameter value which we passed to our annotation and we made a decision out of it.

Conclusion

We have seen how can we,

  • Implement a custom Spring AOP Annotation
  • Accept Parameters for annotation

With this you are now ready to explore and create your own custom annotations.

Custom annotations can be a powerful tool for adding metadata to your code. By using custom annotation, you can make your code more organised and easier to understand. Make sure to give it a try :D

I hope this article helped you understand Spring AOP Annotations a little better.

To get a little more deeper, check out the extension to this article here.

If you liked the work, follow me on Medium for more such articles, and Github for more such repositories.

Resource

All of the code used in this article can be found in this Github Repo

--

--