Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
Chaturmind
← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
HomeLearnSpring BootSpring Boot REST API DevelopmentSpring Framework Internals
✓ FreeAdvanced· 10 min read

Spring AOP

JDK dynamic proxies vs CGLIB, why calling your own method bypasses AOP advice entirely, pointcut expressions, the five advice types, and where @Transactional itself fits into all of this.

Published September 23, 2026


Spring AOP

Bean Lifecycle In Detail already revealed where AOP proxies get created (postProcessAfterInitialization). This lesson covers what those proxies actually do.

Proxy-based AOP: two proxy strategies

Spring AOP works by wrapping a target bean in a proxy that intercepts method calls, runs any matching "advice," and delegates to the real method. Which proxy mechanism gets used depends on what the target implements:

  • JDK dynamic proxy — used when the target class implements at least one interface. The proxy implements the same interface(s), and callers holding an interface-typed reference are transparently talking to the proxy instead of the real object.
  • CGLIB — used when the target class has no interface (or Spring is configured to always prefer CGLIB). CGLIB generates a runtime subclass of the target class itself, overriding its methods to insert the interception logic — this is the exact same technique behind @Configuration class proxying (see Component Scanning & Configuration).

Why self-invocation bypasses AOP entirely

@Service
class OrderService {
    @Transactional
    void placeOrder(Order order) { /* ... */ }

    void bulkPlaceOrders(List<Order> orders) {
        for (Order o : orders) placeOrder(o); // calling 'this.placeOrder()' directly — NOT through the proxy
    }
}

When bulkPlaceOrders calls placeOrder(o), that's a plain Java method call on this — it never goes through the Spring-generated proxy at all, because the call originates from inside the same object, not from an external caller holding the proxy reference. Since @Transactional (and every other AOP advice) is implemented entirely via proxy interception, a call that never reaches the proxy gets none of that advice — placeOrder's transactional behavior, logging, security checks, whatever advice would normally apply, simply doesn't run in this self-invocation case. This trips up nearly everyone the first time, and the standard fixes are: move the self-invoked method to a different bean (so the call comes from outside, through that bean's own proxy), or inject the bean's own proxy reference via ApplicationContext/AopContext.currentProxy() and call through that explicitly.

Pointcut expressions: which methods does advice apply to

@Pointcut("execution(* com.example.service.*.*(..))") // any method, any return type, in any class in this package
void serviceLayer() {}

@Pointcut("within(com.example.repository..*)") // any method in this package or sub-packages
void repositoryLayer() {}

@Pointcut("@annotation(com.example.Loggable)") // any method annotated with @Loggable, regardless of package
void loggableMethods() {}

execution() matches by method signature pattern (package, class, method name, parameter types). within() matches more coarsely, by type/package location. @annotation() matches by the presence of a specific annotation on the method, regardless of where that method lives — this is exactly the mechanism @Transactional itself relies on: Spring's transaction infrastructure is an AOP aspect whose pointcut is @annotation(Transactional).

The five advice types

@Aspect
@Component
class LoggingAspect {
    @Before("serviceLayer()")
    void logBefore(JoinPoint jp) { log.info("Entering " + jp.getSignature()); }

    @After("serviceLayer()")
    void logAfter(JoinPoint jp) { log.info("Exiting " + jp.getSignature()); } // runs regardless of success/failure

    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    void logSuccess(Object result) { log.info("Returned: " + result); } // only on successful return

    @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
    void logFailure(Exception ex) { log.error("Failed", ex); } // only on exception

    @Around("serviceLayer()")
    Object logDuration(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // explicitly invokes the actual target method
        log.info("Took " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

@Around is the most powerful and most different from the rest: it receives a ProceedingJoinPoint and must explicitly call .proceed() to invoke the target method at all — which means @Around advice can decide whether the target method runs, modify its arguments before calling proceed(), or replace its return value entirely after. The other four advice types (Before/After/AfterReturning/AfterThrowing) run automatically around a normal method invocation with no ability to prevent it from executing.

Common AOP use cases

Logging, security checks (verifying a caller's permissions before a method runs), caching (short-circuiting method execution entirely if a cached result exists — an @Around advice deciding not to call proceed()), and — worth calling out explicitly — transaction management itself. @Transactional isn't a special language feature; it's an ordinary AOP aspect (an @Around-style advice) whose pointcut matches @annotation(Transactional), wrapping the target method's execution in transaction begin/commit/rollback logic (the full mechanics are in @Transactional Deep Dive) — the exact same proxy-and-self-invocation caveats covered in this lesson apply to it directly.

Follow-up questions this topic invites — and their answers

Q: If a class implements an interface, does Spring always use a JDK dynamic proxy for it? A: By default, yes, but this is configurable (@EnableAspectJAutoProxy(proxyTargetClass = true) forces CGLIB even for interface-implementing classes) — some teams prefer always-CGLIB for consistency, since JDK dynamic proxies can only proxy methods declared on the implemented interface, which occasionally surprises people when a public method exists on the concrete class but not the interface.

Q: Can @Around advice modify the arguments passed to the target method? A: Yes — ProceedingJoinPoint.proceed(Object[] args) accepts a replacement argument array, letting @Around advice transform inputs before the target method ever sees them, not just intercept the output.

Q: Does AOP advice apply to private methods? A: No — proxy-based AOP can only intercept calls that go through the proxy's public interface, and a private method can never be called from outside the class in the first place, so it's structurally impossible for external-facing proxy interception to apply to it, self-invocation issue aside.

Q: Why is @annotation() pointcut matching so central to how Spring's own built-in features (like @Transactional) work? A: It decouples 'which methods get this behavior' from any specific package or naming convention — a team can put @Transactional on any method anywhere in the codebase and it just works, because the pointcut matches the annotation's presence directly rather than requiring methods to live in a specific package structure for a within()-style pointcut to catch them.

Previous

Auto-Configuration Mechanism

Next

@Transactional Deep Dive

AI Tutor

Lesson: Spring AOP

Quick actions

AI responses can be inaccurate. Verify critical information.