Implement Custom Annotations in Spring Boot for Logging, Security & Caching

This guide explains common scenarios for using custom Java annotations—such as logging, permission control, data validation, and caching—and walks through the step-by-step process of defining an annotation, applying it in Spring services, handling it with an AOP aspect, and testing the implementation in a Spring Boot application.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Implement Custom Annotations in Spring Boot for Logging, Security & Caching

Common Scenarios for Custom Annotations

Logging : Record method entry, exit, parameters, return values and execution time before and after method execution.

Permission Control : Annotate controller methods to specify required permissions, enabling the system to enforce access checks based on the annotation.

Data Validation : Apply annotations on entity fields to validate length, format, etc., e.g., during user registration.

Cache Handling : Mark methods whose return values can be cached, specifying cache name and expiration.

Implementation Steps for Custom Annotations

1. Define the annotation : Use the @interface keyword. An annotation can contain members with default values.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// Define a custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
    // Member variable for a log prefix
    String prefix() default "";
}
@Retention(RetentionPolicy.RUNTIME)

keeps the annotation at runtime for reflection. @Target(ElementType.METHOD) limits the annotation to methods. String prefix() default "" defines a member with a default empty string.

2. Use the annotation : Apply it to target elements, such as a service method.

@Service
public class UserService {
    @LogExecutionTime(prefix = "UserService business method execution time: ")
    public String getUserInfo() {
        try {
            Thread.sleep(1000); // Simulate business processing time
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new User("xuanwu");
    }
}

The getUserInfo method is annotated with @LogExecutionTime and a custom prefix.

3. Process the annotation : Create an AOP aspect that runs before and after the annotated method.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogExecutionTimeAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogExecutionTimeAspect.class);

    @Around("@annotation(logExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object proceed = joinPoint.proceed(); // Execute target method
        long endTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        String prefix = logExecutionTime.prefix();
        logger.info("{} {} method execution time: {} ms", prefix, methodName, (endTime - startTime));
        return proceed;
    }
}
@Aspect

marks the class as an aspect. @Component registers it in the Spring container. @Around("@annotation(logExecutionTime)") defines an around advice that matches methods annotated with @LogExecutionTime. joinPoint.proceed() executes the target method; the aspect logs the execution time using the annotation’s prefix and method name.

4. Test the implementation with a controller.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/info")
    public String getUserInfo() {
        return userService.getUserInfo();
    }
}

Run the Spring Boot application and request /user/info. The log will show something like:

UserService business method execution time: getUserInfo method execution time: 1000 ms
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAOPSpring BootCustom Annotations
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.