Created A Custom Annotation, Now What ?

Shubham Wankhede
5 min readAug 30, 2023

while working with Java and Spring Boot Framework one of the most appealing thing we could find is Annotations, we can literally find hundreds of annotations in different starters of Spring Boot or from other libraries like Lombok which makes our day to day life easier.

We also know that we can create such kind custom annotation, but the problem is that most of developers who are new to Java don’t know how to process them, how to write the logic around these annotation.

Let’s try to understand it in this blog.

We will discuss this topic in this blog in two parts
1. Creating Custom Annotations
2. Processing Custom Annotations

Creating Custom Annotation

creating a custom annotation is one of the most easy thing in java, things we have to consider while creating custom annotations are :

  1. Make class as annotation : replace “class” keyword with “@interface
  2. Specify When to Process this annotation / when this annotation will be available (Runtime/CompileTime etc) : using @Retention predefined annotation and value though RetentionPolicy enum
  3. Specify where you want to apply this annotation (Method/Class/Field etc) : using @Target predefined annotation and value through ElementType enum, you can specify more than one value as a array

with you can create a Custom Annotation as given below

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

// ElementType.TYPE : To use this annotation at class level
// ElementType.METHOD : To use this annotation at method level
// ElementType.FIELD : To use this annotation at class level

// RetentionPolicy.SOURCE : The annotation is only available in the source code and is not included in compiled class files.
// RetentionPolicy.CLASS : The annotation is included in the compiled class files but is not available at runtime.
// RetentionPolicy.RUNTIME: The annotation is available at runtime through reflection.

we can also add some parameters/elements to pass values to these annotation with or without default value, if you are providing the default value for parameter it becomes option whenever you are using the annotation.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogPerformance {
String value();
String unit() default "sec";
}

We are done.

Yes, it’s this simple.

In case if you want to use the functionalities of some other existing annotations lest say predefined annotation then we can do it by annotating the predefined annotation on top of our custom annotation, but make sure that the predefined annotation has Target type as ElementType.TYPE and it’s intended to use it like that

Ex. in @PostMapping annotation of Spring Web library we see the same

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(
method = {RequestMethod.POST}
)
public @interface PostMapping {
.........
}

Processing Custom Annotation

There are multiple ways to process the annotations depending on what you are trying to achieve whether you want to modify bean or execute some logic when executing some methods etc.

If you are working with Spring Boot some of most common approaches are using Post Processors and Spring AOP

Let’s understand Processing Custom Annotations using Spring AOP, since it provides way many features for us.

Step 1 : to work with spring AOP we need below maven dependency in your pom.xml file

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

Step 2 : after that to enable AOP in your spring boot application add @EnableAspectJAutoProxy annotation on top of starter class or any @Configuration annotated class

@SpringBootApplication
@EnableAspectJAutoProxy
public class LiscenceServiceApplication {
public static void main(String[] args){
SpringApplication.run(LiscenceServiceApplication.class, args);
}
}

Step 3 : Now comes the part where you have to write the actual code to process the custom annotation

Few of the main components of AOP is

  1. Aspect : Aspect encapsulates the cross-cutting concerns.
  2. Advice : actual code that is executed at a specific join point, it defines the behaviour.
  3. Pointcut : set of join point where advice should be applied. it defines the criteria for selecting specific location or method in programs execution
  4. Join Point : specific point in programs execution

there are other things as well like weaving, proxy, target object etc.

Now, to make a class as Aspect in Spring AOP we have to use annotation @Aspect and

to Specify advice we have to use one of the annotations from below based on our requirement
@Before
@AfterReturning
@AfterThrowing
@After
@Around

The name itself of these advices are self explanatory on when to use which one along with using the advice annotation we have to provide the value which is a pointcut expression to specify the criteria for executing the advice

How to write pointcut expression

  1. Declaration related expression : execution(* *.*.*(..))
    Ex : execution(public * com.example.demo.MyService.*(..))
  2. For Bean Pointcut : bean(beanNamePattern)
    Ex : bean(*Controller)
  3. For Annotation Pointcut : @annotation(annotation-class-type)
    Ex : @annotation(com.ls.MyAnnotation.class)
  4. For With Pointcut : within(type-pattern)
    Ex : within(com.ls.*Controller)

You can also combine more than one pointcuts using || and && and ! operators

Now let’s create our class to write custom annotation processing logic

A@Aspect
@Component
@Slf4j
public class LogPerformanceAspect {

@Around("@annotation(com.ls.LogPerformance)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
int start = LocalTime.now().getNano();
// Proceed with the original method
Object obj = joinPoint.proceed();
int end = LocalTime.now().getNano();
log.info("Perf : "+(end-start));

//if you want to get the parameters values you can use below approach
LogPerformance logPerformance = ((MethodSignature)joinPoint.getSignature())
.getMethod()
.getAnnotation(LogPerformance.class);

String unit = logPerformance.unit();
String value = logPerformance.value();

return obj;
}
}

For @Around advice if you are not sending the modified object we can keep the return type as Void

Now we are done, let test this.

Testing

To test this I’ve created a controller class with some random endpoint and added this annotation and added this annotation top of the endpoint method

@RestController
public class TestingController {

@LogPerformance(value = "random",unit = "nano")
@GetMapping("/test")
public String getLicense(){
return "Tested";
}
}

start the server on any available port, I’m starting the application on 9011, and hit the /test endpoint and check the log

and I can see the log.

and we are done . :)

I would highly recommend you to go through Spring Boot AOP documentation to get details on how to work with spring AOP .

--

--