Skip to content

Spring Boot AOP (Aspect Oriented Programming)

Introduction: The Security Guard Analogy

Imagine a large office building with dozens of rooms. Every time someone enters a room, a guard must log the entry, check credentials, and log the exit. Without a central system, you would need a separate guard stationed inside every single room — that is 100 guards doing the exact same logging work for 100 rooms.

Aspect Oriented Programming (AOP) is the central logging system that replaces all those individual guards. Instead of writing logging, transaction management, or security code in every method, you write it once in one dedicated place — an aspect — and AOP automatically applies it wherever you need it.

This is exactly what AOP is: it lets you focus on your core business logic by handling boilerplate and repetitive code like logging and transaction management separately.


Why AOP Exists

Consider a typical application with 100 service methods. Each one needs:

  • Logging before and after execution
  • Transaction start and rollback logic
  • Security checks

Without AOP you write the same logging and transaction code in all 100 methods. AOP separates these cross cutting concerns from your business logic, placing them in a single reusable module called an aspect.

Benefits:

  • Reusability: The same logging logic works for all 100 methods without duplication.
  • Maintainability: Change logging behavior in one place and every method inherits the update automatically.
  • Separation of concerns: Your service methods contain only business logic.

Dependency Setup

Add the AOP starter to your pom.xml:

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

Core AOP Terminology

1. Aspect

A class that contains cross cutting logic (logging, transactions, etc.). Annotated with @Aspect and @Component.

2. Pointcut

An expression that identifies which methods an advice should apply to. It is the "filter" that tells AOP where to intercept.

3. Advice

The actual code that runs before, after, or around the matched method. Types:

  • @Before — runs before the target method
  • @After — runs after the target method (always, like finally)
  • @Around — surrounds execution; you manually call proceed() to invoke the target

4. Join Point

The specific moment where the actual method invocation happens.


Types of Pointcut Expressions

execution — Match a Specific Method

java
@Aspect
@Component
public class LoggingAspect {

    // Match fetchEmployee() in EmployeeController — no arguments
    @Before("execution(* com.example.learning.springboot.EmployeeController.fetchEmployee())")
    public void beforeFetchEmployee() {
        System.out.println("Inside before method aspect");
    }
}

Parts of the expression:

execution( [access-modifier?] [return-type] [full-method-path]([params]) )
  • Access modifier — optional; omitting it matches public, private, and protected
  • Return type — required; use * to match any type
  • Method path — package + class + method name
  • Parameters — use () for none, (String) for specific types

Wildcards:

  • * — matches any single item (any return type, any method name)
  • .. — matches zero or more (zero or more parameters, any subpackage)
java
// Any method in EmployeeUtil with any return type, no arguments
"execution(* com.example.EmployeeUtil.*())"

// Any method in EmployeeUtil accepting a String
"execution(* com.example.EmployeeUtil.*(String))"

// Any method in any class under com.example and its subpackages
"execution(* com.example..*.*(..))"

within — Match All Methods in a Class or Package

java
// All methods in EmployeeUtil
@Before("within(com.example.EmployeeUtil)")
public void withinClass() { ... }

// All methods in any class under a package
@Before("within(com.example.springboot..*)")
public void withinPackage() { ... }

@within — All Methods in Classes with a Specific Annotation

java
// All methods in classes annotated with @Service
@Before("@within(org.springframework.stereotype.Service)")
public void withinServiceAnnotatedClasses() { ... }

@annotation — Methods Carrying a Specific Annotation

java
// Any method annotated with @GetMapping
@Before("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void beforeGetMappingMethods() { ... }

args — Methods with Specific Argument Types

java
// Any method accepting (String, int)
@Before("args(String, int)")
public void beforeStringIntMethod() { ... }

// Any method accepting an argument of a specific class
@Before("args(com.example.EmployeeDto)")
public void beforeEmployeeDto() { ... }

@args — Methods Where the Argument's Class Has a Specific Annotation

java
// Argument class must be annotated with @Service
@Before("@args(org.springframework.stereotype.Service)")
public void beforeAnnotatedArgument() { ... }

target — Methods Called on a Specific Instance

java
// Any method called on an EmployeeUtil instance (or its subclasses if interface given)
@Before("target(com.example.EmployeeUtil)")
public void targetEmployeeUtil() { ... }

// Using an interface — matches all implementing classes
@Before("target(com.example.Employee)")
public void targetEmployeeInterface() { ... }

Combining Pointcuts

Use && (and), || (or) to combine expressions:

java
@Before("execution(* com.example.EmployeeController.*(..)) && @within(org.springframework.web.bind.annotation.RestController)")
public void andExample() { ... }

@Before("execution(* com.example.EmployeeController.*(..)) || within(com.example.EmployeeController)")
public void orExample() { ... }

Named Pointcuts

Avoid repeating the same expression by giving it a name:

java
@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.EmployeeUtil.*(..))")
    public void employeeUtilMethods() {} // empty method — just a name

    @Before("employeeUtilMethods()")
    public void beforeAdvice() {
        System.out.println("Before employee util method");
    }

    @After("employeeUtilMethods()")
    public void afterAdvice() {
        System.out.println("After employee util method");
    }
}

Types of Advice in Detail

@Before

java
@Before("execution(* com.example.EmployeeUtil.fetchEmployee(..))")
public void logBefore(JoinPoint joinPoint) {
    System.out.println("Calling: " + joinPoint.getSignature().getName());
}

@After

java
@After("execution(* com.example.EmployeeUtil.fetchEmployee(..))")
public void logAfter(JoinPoint joinPoint) {
    System.out.println("Finished: " + joinPoint.getSignature().getName());
}

@Around

@Around is the most powerful — you control when and whether the actual method is called:

java
@Around("execution(* com.example.EmployeeUtil.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Before: " + joinPoint.getSignature().getName());

    Object result = joinPoint.proceed(); // invoke the actual method

    System.out.println("After: " + joinPoint.getSignature().getName());
    return result;
}

Without calling joinPoint.proceed(), the actual method never executes — powerful for caching, short-circuiting, and security guards.


Complete Working Example

java
// EmployeeUtil.java
@Service
public class EmployeeUtil {

    public String fetchEmployee() {
        System.out.println("Fetching employee details...");
        return "Employee Data";
    }

    public void saveEmployee(String name) {
        System.out.println("Saving employee: " + name);
    }
}

// EmployeeController.java
@RestController
@RequestMapping("/api")
public class EmployeeController {

    @Autowired
    private EmployeeUtil employeeUtil;

    @GetMapping("/fetch-employee")
    public String fetchEmployee() {
        System.out.println("Inside controller: " + Thread.currentThread().getName());
        return employeeUtil.fetchEmployee();
    }
}

// LoggingAspect.java
@Aspect
@Component
public class LoggingAspect {

    // Named pointcut
    @Pointcut("execution(* com.example.EmployeeUtil.*(..))")
    public void allEmployeeUtilMethods() {}

    @Before("allEmployeeUtilMethods()")
    public void logBefore(JoinPoint jp) {
        System.out.println("[BEFORE] Method: " + jp.getSignature().getName());
        System.out.println("[BEFORE] Args: " + Arrays.toString(jp.getArgs()));
    }

    @After("allEmployeeUtilMethods()")
    public void logAfter(JoinPoint jp) {
        System.out.println("[AFTER] Method: " + jp.getSignature().getName());
    }

    @Around("within(com.example..*) && @annotation(org.springframework.web.bind.annotation.GetMapping)")
    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long elapsed = System.currentTimeMillis() - start;
        System.out.println("[AROUND] Elapsed: " + elapsed + "ms");
        return result;
    }
}

Output when /api/fetch-employee is called:

Inside controller: http-nio-8080-exec-1
[BEFORE] Method: fetchEmployee
[BEFORE] Args: []
Fetching employee details...
[AFTER] Method: fetchEmployee
[AROUND] Elapsed: 12ms

How AOP Works Internally (Proxy Mechanism)

This is the most important part to understand deeply — and a very common interview topic.

Step 1: Application Startup — Parse Pointcuts

Spring Boot scans for classes annotated with @Aspect and parses all pointcut expressions, storing them in an efficient data structure for fast matching.

Step 2: Identify Beans Eligible for Interception

Spring Boot goes through all beans (@Component, @Service, @Controller, etc.) and checks whether any parsed pointcut expression could match a method in that class.

Step 3: Create Proxy Classes

For beans eligible for interception, Spring Boot creates a proxy class instead of using the bean directly:

  • JDK Dynamic Proxy — used when the class implements at least one interface. Spring creates a new class that also implements the same interface.
  • CGLIB Proxy — used when the class does not implement any interface. CGLIB creates a subclass of the original class and overrides the relevant methods.

Step 4: Inject Proxy Instead of Real Bean

Anywhere @Autowired EmployeeUtil employeeUtil is used, Spring injects the proxy, not the real EmployeeUtil.

Step 5: Interception at Runtime

When a method is called on the proxy, the proxy's overridden method runs Spring's interception chain:

  1. Collect all matching advice for this method
  2. Build a chain (like a linked list of interceptors)
  3. Execute @Before advice
  4. Call proceed() which triggers @After and @Around advice in the proper order
  5. Invoke the actual method
  6. Unwind the chain executing post advice
Request → Proxy.fetchEmployee() → Before Advice → Real fetchEmployee() → After Advice → Response

This chain is implemented in ReflectiveMethodInvocation.proceed() using a counter that walks through the advice list recursively.


Summary

ConceptDescription
@AspectMarks a class as containing AOP logic
@ComponentRequired on aspect class so Spring manages its lifecycle
PointcutExpression defining which methods to intercept
@BeforeAdvice that runs before the matched method
@AfterAdvice that always runs after (like finally)
@AroundSurrounds execution; must call proceed()
JDK ProxyUsed when target class implements an interface
CGLIB ProxyUsed when target class has no interface

Interview Questions

Q1. What is AOP and what problem does it solve? AOP (Aspect Oriented Programming) solves the problem of cross cutting concerns like logging, security, and transaction management that are repeated across many methods. It lets you write that logic once in an aspect and apply it to any number of methods through pointcut expressions, achieving reusability and separation of concerns.

Q2. What is the difference between a pointcut and an advice? A pointcut is an expression that identifies where (which methods) to apply logic. An advice is the what — the actual code to run. Together: "at these methods (pointcut), run this code (advice) before/after/around."

Q3. What are the different types of advice?@Before runs before the method, @After runs after it (even on exception, like finally), @AfterReturning runs only on successful return, @AfterThrowing runs only when an exception is thrown, and @Around surrounds execution and requires a manual proceed() call to invoke the actual method.

Q4. What is the difference between JDK Dynamic Proxy and CGLIB? JDK Dynamic Proxy is used when the target class implements at least one interface — it creates a new class implementing the same interface. CGLIB is used when the class has no interface — it creates a subclass using code generation. CGLIB can proxy any class; JDK proxies require an interface.

Q5. What are the .. and * wildcards in pointcut expressions?* matches any single item — one return type, one method name, or one parameter. .. matches zero or more items — zero or more arguments, or any subpackage hierarchy.

Q6. When would @Around be preferred over @Before and @After? When you need to conditionally skip the actual method (caching, authorization checks), measure elapsed time across both sides of execution, catch exceptions and transform them, or modify the return value. @Around gives full control over the method invocation.

Q7. Explain how AOP works internally with proxies. At startup, Spring parses all @Aspect classes and stores their pointcut expressions. For each bean that matches any pointcut, Spring creates a proxy (JDK or CGLIB) that wraps the bean. When a method is called through the proxy, the proxy builds a chain of matching advice and executes them in order — before advice runs first, then the real method, then after advice. This happens through ReflectiveMethodInvocation.proceed().

Q8. What is a named pointcut and why is it useful? A named pointcut uses @Pointcut on an empty method to assign a name to a pointcut expression. Instead of repeating the expression in every @Before, @After, or @Around, you reference the method name. This avoids duplication and makes the expression easy to change in one place.

Q9. Can you combine pointcut expressions? How? Yes, using && (and) and || (or) boolean operators. For example: "execution(* com.example.Controller.*(..)) && @within(org.springframework.web.bind.annotation.RestController)" matches only methods in classes that are also annotated with @RestController.

Q10. What is the target pointcut type and how does using an interface differ from a class?target(ClassName) matches any method call made on an instance of that class. When you provide an interface instead of a direct class, the pointcut matches all implementing classes — so any method on a TempEmployee or PermanentEmployee would match if both implement the Employee interface used in the target expression.