Mastering Spring Boot AOP: Custom Annotations, Interceptors, and Self‑Invocation Pitfalls

This tutorial explains the core concepts of Spring AOP, shows how to integrate AOP in Spring Boot to create custom annotations and interceptors, and provides practical solutions for the common self‑invocation issue that causes AOP advice to be ignored.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Mastering Spring Boot AOP: Custom Annotations, Interceptors, and Self‑Invocation Pitfalls

Introduction

This guide shows how to create a custom annotation in Spring Boot and integrate it with Spring AOP to implement cross‑cutting concerns such as logging.

Core AOP Concepts

Aspect : a modular unit of cross‑cutting logic, declared with @Aspect on a class.

Joinpoint : a specific point during program execution (e.g., method invocation or exception handling).

Advice : the action taken at a joinpoint. Spring supports five advice types: @Before: runs before the joinpoint. @After: runs after the joinpoint (regardless of outcome). @AfterReturning: runs after a normal return. @AfterThrowing: runs when an exception is thrown. @Around: surrounds the joinpoint, allowing code before and after the method execution.

Pointcut : a predicate that selects which joinpoints an advice applies to, often expressed with annotation matching.

Target Object : the original bean that receives advice; Spring creates a proxy for it at runtime.

AOP Proxy : the proxy instance (JDK dynamic proxy or CGLIB) that delegates calls to the target while applying advice.

Weaving : the process of linking aspects to target objects, performed at compile time, load time, or runtime (Spring uses runtime weaving).

Integrating AOP in a Spring Boot Project

Add the AOP starter dependency to pom.xml:

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

Enable proxy creation by annotating a configuration class with @EnableAspectJAutoProxy. The annotation imports AspectJAutoProxyRegistrar, which registers the AnnotationAwareAspectJAutoProxyCreator bean.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {}

Define a Custom Annotation

Example: a logging annotation that can carry an optional description.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    String value() default "";
}

Define an Aspect

The aspect must be a Spring bean (e.g., annotated with @Component) and marked with @Aspect. Use @Order to control execution precedence when multiple aspects exist.

@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SysLogAspect {
    // pointcut and advice definitions go here
}

Pointcut Expression

Intercept any method annotated with @SysLog:

@Pointcut("@annotation(com.example.annotation_demo.annotation.SysLog)")
public void pointCut() {}

Around Advice

Record execution time and delegate to a placeholder log‑saving method:

@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
    long beginTime = System.currentTimeMillis();
    Object result = point.proceed();
    // TODO: implement log persistence
    saveLog(point, beginTime);
    return result;
}

Usage Example

Apply @SysLog to a controller method:

@SysLog
@PostMapping("/add")
public String add() {
    return "";
}

Creating a Custom Annotation with a HandlerInterceptor

Interceptors run before controller methods, making them suitable for concerns such as duplicate‑submission protection.

Duplicate‑Submission Annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /** Default expiration time: 5 seconds */
    long seconds() default 5;
}

Interceptor Implementation

@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            RepeatSubmit repeat = AnnotationUtils.findAnnotation(hm.getMethod(), RepeatSubmit.class);
            if (repeat == null) return true;
            String flag = ""; // build a unique key, e.g., IP+userId+uri+params
            Boolean absent = stringRedisTemplate.opsForValue()
                .setIfAbsent(flag, "", repeat.seconds(), TimeUnit.SECONDS);
            if (absent != null && !absent) {
                throw new RepeatSubmitException();
            }
        }
        return true;
    }
}

Register the interceptor in Spring MVC configuration (e.g., by extending WebMvcConfigurer and overriding addInterceptors).

Testing the Interceptor

@RepeatSubmit
@GetMapping("/add")
public String add() {
    return "";
}

Self‑Invocation Causing AOP Advice Loss

When a method annotated with advice calls another method in the same class via this, the call bypasses the proxy, so the advice is not applied.

public class ArticleServiceImpl {
    @SysLog
    public void A() { /* ... */ }

    public void B() {
        this.A(); // advice on A() is ignored
    }
}

Solution 1 – Inject the Bean Itself

public class ArticleServiceImpl {
    @Autowired
    private ArticleService articleService; // self‑bean injected

    @SysLog
    public void A() { /* ... */ }

    public void B() {
        articleService.A(); // goes through proxy
    }
}

Solution 2 – Retrieve Bean from ApplicationContext

public class ArticleServiceImpl {
    @SysLog
    public void A() { /* ... */ }

    public void B() {
        ApplicationContextUtils.getApplicationContext()
            .getBean(ArticleService.class).A();
    }
}

Solution 3 – Use AopContext to Access the Current Proxy

Enable proxy exposure: @EnableAspectJAutoProxy(exposeProxy = true).

public class ArticleServiceImpl {
    @SysLog
    public void A() { /* ... */ }

    public void B() {
        ((ArticleService) AopContext.currentProxy()).A();
    }
}

Conclusion

The article covered fundamental AOP terminology, demonstrated how to integrate Spring AOP with a custom annotation, showed an alternative interceptor‑based approach, and provided three concrete techniques to avoid self‑invocation loss of AOP advice.

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.

aopSpring BootCustom AnnotationInterceptorSelf-invocation
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.