Spring Boot AOP: How Proxies Work and Why Advice Gets Skipped

Spring Boot AOP can intercept calls to Spring-managed beans for concerns such as transaction boundaries, metrics, and audit logging. The production detail that catches teams out is the proxy boundary: a call from another bean can pass through advice, but a method calling another method on this (this.someMethod()) bypasses the proxy, so the advice never runs. This guide explains how Spring AOP proxies work, how to recognize self-invocation problems, and where AOP belongs in a Spring Boot service.

What is Aspect-Oriented Programming (AOP)?

Aspect-Oriented Programming (AOP) is a programming paradigm that can modularize cross-cutting concerns in an application. These concerns are aspects of the application that affect multiple parts of the code, such as:

  • Logging
  • Transaction management
  • Security (e.g., authentication and authorization)
  • Exception handling
  • Performance monitoring

Instead of scattering code throughout multiple classes, AOP enables a cleaner, more modular approach, allowing developers to separate business logic from concerns like logging or security.

Key Concepts of AOP in Spring Boot

Spring Boot provides built-in support for AOP through the Spring AOP module, which is based on the Proxy Pattern and allows for method interception. The key concepts in AOP are:

1. Aspect

  • A modular unit of cross-cutting concern.
  • Implemented as a class annotated with @Aspect.

2. Join Point

  • A specific point during the program’s execution at which an aspect can be applied.
  • In Spring AOP, join points are method executions.

3. Advice

  • The actual action taken at a particular join point.
  • Types of advice:
  • Before Advice: Runs before a method execution (@Before).
  • After Advice: Runs after a method execution (@After).
  • After Returning Advice: Runs after a method completes (@AfterReturning).
  • After Throwing Advice: Runs when a method throws an exception (@AfterThrowing).
  • Around Advice: Wraps around a method and can control whether the method is executed (@Around).

4. Pointcut

  • A predicate that matches join points.
  • Defined using @Pointcut annotation.

5. Target

  • The object whose method execution is being advised.

6. Weaving

  • The process of linking aspects with the target objects.
  • Spring AOP uses runtime weaving through dynamic proxies.

Setting Up AOP in Spring Boot

To use AOP in Spring Boot, follow these steps:

Directory structure

src/
 ├── main/
 │    ├── java/
 │    │    ├── com/
 │    │    │    ├── example/
 │    │    │    │    ├── programming/
 │    │    │    │    │    ├── config/
 │    │    │    │    │    │    ├── AopConfig.java
 │    │    │    │    │    ├── common/
 │    │    │    │    │    │    ├── aop/
 │    │    │    │    │    │    │    ├── LoggingAspect.java  <-- AOP class (optional)
 │    │    │    │    │    ├── controller/
 │    │    │    │    │    │    ├── aop/
 │    │    │    │    │    │    │    ├── TestController.java
 │    │    │    │    │    ├── service/
 │    │    │    │    │    │    ├── aop/
 │    │    │    │    │    │    │    ├── TestService.java  <-- Your service class
 │    ├── resources/
 │    │    ├── application.properties (or application.yml)

Step 1: Add Spring AOP Dependency

Add spring-boot-starter-aop to your build to use @Aspect-based Spring AOP. When the dependency is on the classpath, Spring Boot auto-configuration enables proxy support; you normally do not need to add @EnableAspectJAutoProxy yourself.

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

Step 2: Understand Spring Boot’s Proxy Auto-Configuration

If your application has the spring-boot-starter-aop dependency, Spring Boot auto-configuration enables AspectJ auto-proxy support. You usually do not need an @EnableAspectJAutoProxy configuration class in a Spring Boot application.

// In Spring Boot, the AOP starter enables proxy-based advice through auto-configuration.
// No separate @EnableAspectJAutoProxy config class is normally required.

Step 3: Create an Aspect

Define an aspect with the @Aspect annotation and apply advice.

Example: Logging Aspect

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Before("execution(* com.example.programming.service.aop.TestService.testMethod(..))")
    public void logBeforeMethodExecution() {
        log.info("Before executing method...");
    }

    @After("execution(* com.example.programming.service.aop.TestService.testMethod(..))")
    public void logAfterMethodExecution() {
        log.info("After executing method...");
    }

}

@Before Annotation is an AOP annotation that allows you to execute custom logic before the actual method execution.

@After Annotation is an AOP annotation that will enable you to execute custom logic after the actual method execution.

Example: Logging Method Return Values

@Aspect
@Component
public class LoggingAspect {

    @AfterReturning(pointcut = "execution(* com.example.programming.service.aop.TestService.testMethod(..))", returning = "result")
    public void logMethodReturn(JoinPoint joinPoint, Object result) {
        System.out.println(joinPoint.getSignature() + " returned: " + result);
    }
}

Explanation

@AfterReturningAdvice

  • This advice executes after a method in the com.example.programming.service.aop.TestService.testMethod package successfully returns a value (i.e., without throwing an exception).
  • It is commonly used for logging, auditing, or modifying return values.

JoinPoint Class is an interface in Spring AOP that provides metadata about the execution of the intercepted method. It is commonly used in advice methods to access details like:

  1. Method Signature â†’ joinPoint.getSignature()
  2. Method Arguments â†’ joinPoint.getArgs()
  3. Target Object (Bean instance) â†’ joinPoint.getTarget()

Advanced AOP Use Cases

1. Using @Around Advice for Execution Time Logging

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

@Aspect
@Component
public class PerformanceAspect {
    @Around("execution(* com.example.programming.service.aop.TestService.testMethod(..))")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            long elapsedNanos = System.nanoTime() - start;
            System.out.println(joinPoint.getSignature() + " executed in " + elapsedNanos + " ns");
        }
    }
}

@Around is an AOP annotation that allows you to execute custom logic before and after the method execution.

ProceedingJoinPoint is a subinterface of JoinPoint used in @Around advice in Spring AOP. It provides additional functionality to control the method execution flow.

  • ProceedingJoinPoint allows you to:
  1. Proceed with method execution: joinPoint.proceed()
  2. Access method signature and arguments: Similar to JoinPoint

2. Handling Exceptions with @AfterThrowing

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ExceptionHandlingAspect {

    @AfterThrowing(pointcut = "execution(* com.example.programming.service.aop.TestService.throwExceptionMethod(..))", throwing = "ex")
    public void logException(Exception ex) {
        System.out.println("Exception thrown: " + ex.getMessage());
    }
}

@AfterThrowing is an AOP annotation that runs only when the target method throws an exception.

3. Using Pointcut Expressions for Fine-Grained Control

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

    @Pointcut("execution(* com.example.programming.service.aop.TestService.testMethod(..))")
    public void testServiceMethods() {}

    // Before Advice: Runs before the method execution
    @Before("testServiceMethods()")
    public void beforeAdvice(JoinPoint joinPoint) {
        log.info("Before: {}", joinPoint.getSignature());
    }
}

Pointcut annotation(@Pointcut) is an annotation in Spring AOP used to define a reusable method representing a set of join points (where an aspect can be applied).

Create a RESTful API for testing Spring AOP.

Create the Service

import org.springframework.stereotype.Service;

@Service
public class TestService {

    public String testMethod(String name) {
        return "Hello, " + name;
    }

    public String throwExceptionMethod() {
        throw new RuntimeException("Something went wrong!");
    }
}

Create the RestController

import com.example.programming.service.aop.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class TestController {

    private final TestService testService;

    @GetMapping("/greet/{name}")
    public String greet(@PathVariable String name) {
        return testService.testMethod(name);
    }

    @GetMapping("/error")
    public String error() {
        return testService.throwExceptionMethod();
    }
}

Testing AOP in Action:

  1. Start your Spring Boot application.
  2. Open your browser or API testing tool (e.g., Postman) and test the following endpoints:
  • GET /api/test/greet/{name} (e.g., GET /api/test/greet/John)
  • GET /api/test/error (This will trigger an exception.)

Test case Before and After annotation

localhost:8080/api/test/greet/John
request
response

Console log

c.e.p.common.aop.LoggingAspect: Before executing method...
c.e.p.common.aop.LoggingAspect: After executing method...

Test case AfterReturning annotation

localhost:8080/api/test/greet/John
request
response

Console log

c.e.p.common.aop.LoggingAspect: String com.example.programming.service.aop.TestService.testMethod(String) returned: Hello, John

Test case around annotation

localhost:8080/api/test/greet/John
request
response

Console log

c.e.p.common.aop.LoggingAspect: String com.example.programming.service.aop.TestService.testMethod(String) executed in 149800ns

Test case AfterThrowing annotation

localhost:8080/api/test/error
request
response

Console log

c.e.p.common.aop.LoggingAspect: Exception thrown: Something went wrong!

Test case Pointcut Expressions

localhost:8080/api/test/greet/John
request
response

Console log

c.e.p.common.aop.LoggingAspect: Before: String com.example.programming.service.aop.TestService.testMethod(String)

Benefits of Using AOP in Spring Boot

1. Separation of Concerns

  • Keeps business logic clean by moving cross-cutting concerns to separate aspects.

2. Code Reusability

  • Aspects can be reused across multiple modules.

3. Maintainability

  • Centralized handling of concerns like logging and security makes the code easier to maintain.

4. Performance Optimization

  • Allows performance monitoring and optimization without modifying the core business logic.

5. Security Enforcement

  • It can be used to enforce authentication and authorization at the method level.

When to Avoid AOP

While AOP is powerful, it should be used with caution. Scenarios where AOP may not be ideal:

  • When one method calls another method on the same bean, the call bypasses the Spring proxy, so advice such as @Transactional or @Around does not run on that internal call. Move the advised method to a separate Spring bean and call it through the injected bean.
  • When debugging becomes difficult due to method interceptions.
  • Using AOP excessively makes the code harder to understand and maintain.

Conclusion

Spring AOP works at the proxy boundary: calls routed through a Spring-managed proxy can be advised, while self-invocation bypasses it. Use aspects for cross-cutting concerns such as transactions and lightweight metrics, and test the call path when advice is part of correctness.

This article was originally published on Medium.

Leave a Comment

Your email address will not be published. Required fields are marked *