> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deep Dive: Spring AOP

> Aspect Oriented Programming internals

# Spring AOP (Aspect Oriented Programming)

AOP is one of the most powerful (and misunderstood) parts of Spring. It allows you to separate **Cross-Cutting Concerns** (logging, security, transactions) from your Business Logic.

**Real-world analogy:** Think of AOP like airport security. Every passenger (method call) goes through the same security checkpoint (aspect), regardless of their destination gate (business logic). The security staff do not need to know where you are flying -- they apply the same screening rules to everyone. Your gate agent (service method) does not need to know about security -- they focus purely on boarding. AOP lets you separate "things that apply to many methods" from "the actual work each method does." Without AOP, every gate agent would need their own security screening -- duplicated, inconsistent, and error-prone.

## 1. The Concepts

* **Aspect:** A module that encapsulates a concern (e.g., `LoggingAspect`). Think of it as a blueprint for what to do and where to do it.
* **Join Point:** A point in execution where an aspect can be applied (e.g., "Method execution of `saveUser`"). In Spring AOP, join points are always method executions -- unlike AspectJ, which can also intercept field access and constructor calls.
* **Advice:** The action taken at a join point (e.g., "Log 'Enter method' before the method runs"). The "what to do."
* **Pointcut:** A predicate expression that matches join points (e.g., "All methods in `com.service` package"). The "where to do it."
* **Weaving:** The process of linking aspects with application objects to create an advised (proxied) object. Spring does this at runtime using dynamic proxies. AspectJ can do it at compile time for better performance.

## 2. Advice Types

| Advice            | Description                                                                            | Use Case                                  |
| :---------------- | :------------------------------------------------------------------------------------- | :---------------------------------------- |
| `@Before`         | Runs before the method.                                                                | Auth checks, Logging arguments.           |
| `@AfterReturning` | Runs after successful execution.                                                       | Audit logging "Success".                  |
| `@AfterThrowing`  | Runs if exception is thrown.                                                           | Error reporting.                          |
| `@After`          | Runs finally (finally block).                                                          | Cleanup.                                  |
| `@Around`         | **Most Powerful**. Wraps the method. Can change args, return value, or stop execution. | Transaction Mgmt, Performance Monitoring. |

## 3. Implementation Example: Performance Monitoring

```java theme={null}
@Aspect     // Marks this class as an aspect -- Spring scans for this annotation
@Component  // Must also be a Spring bean to be discovered
@Slf4j      // Lombok: generates a 'log' field (SLF4J logger)
public class PerformanceAspect {

    // Pointcut expression breakdown:
    //   execution(          -- match method execution join points
    //     *                 -- any return type
    //     com.devweekends.demo.service  -- in this package
    //     .*                -- any class in that package
    //     .*                -- any method name
    //     (..)              -- with any number/type of arguments
    //   )
    @Pointcut("execution(* com.devweekends.demo.service.*.*(..))")
    public void serviceMethods() {} // Empty body -- this is just a named reference

    @Around("serviceMethods()") // @Around wraps the entire method execution
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        // proceed() calls the actual target method.
        // Without this call, the target method NEVER executes.
        // This is what makes @Around so powerful -- you control whether
        // the method runs, and you can modify the arguments or return value.
        Object result = joinPoint.proceed(); 
        
        long end = System.currentTimeMillis();
        log.info("{} took {} ms", joinPoint.getSignature().getName(), (end - start));
        
        // You must return the result, or the caller gets null.
        return result;
    }
}
```

**Production tip:** For real performance monitoring, use Micrometer's `@Timed` annotation instead of building your own aspect. It integrates with Prometheus/Grafana and gives you percentiles, histograms, and alerting for free. The custom aspect above is great for understanding how AOP works, but in production you want metrics that are aggregated, exported, and dashboarded -- not log lines.

## 4. How it Works: The Proxy Pattern

Spring AOP uses **Dynamic Proxies**.

<div className="flex justify-center my-4">
  <div className="w-full max-w-3xl">
    ```mermaid theme={null}
    sequenceDiagram
        participant Caller
        participant Proxy as AOP Proxy
        participant Aspect as LoggingAspect
        participant Target as UserService

        Caller->>Proxy: saveUser()
        Proxy->>Aspect: @Before logic
        Aspect-->>Proxy: ok
        Proxy->>Target: saveUser()
        Target-->>Proxy: return
        Proxy->>Aspect: @After logic
        Aspect-->>Proxy: ok
        Proxy-->>Caller: return
    ```
  </div>
</div>

### JDK vs CGLIB

* **JDK Dynamic Proxy:** Used if the target implements an **Interface**. Only creates proxies for interfaces.
* **CGLIB (Code Generation Library):** Used if the target is a **Class** (no interface). Spring generates a subclass of your bean at runtime.

> **Interview Tip:** Since Spring Boot 2.0, CGLIB is the default (even for interfaces) to ensure consistency (prevents `ClassCastException` if you autowire the impl class).

## 5. Pitfall: Self-Invocation

This is the single most common AOP bug in Spring, and it bites developers at every experience level.

**Problem:** Using `@Transactional` or `@Async` on a method called from *within the same class* does not work.

```java theme={null}
@Service
public class OrderService {
    public void createOrder() {
        // ...
        sendEmail(); // THIS WILL NOT BE ASYNC! The @Async annotation is ignored.
    }

    @Async
    public void sendEmail() { ... }
}
```

**Reason:** When Spring creates the `OrderService` bean, it wraps it in a proxy. External callers (controllers, other services) call the proxy, which intercepts `@Async` and runs it on a separate thread. But inside `createOrder()`, the call to `sendEmail()` is `this.sendEmail()` -- `this` refers to the **Target Object** (the real `OrderService`), not the proxy. The proxy is completely bypassed, and the `@Async` annotation is invisible.

**Solutions (pick one):**

```java theme={null}
// Solution 1: Move the method to a separate service (cleanest)
@Service
public class EmailService {
    @Async
    public void sendEmail() { ... } // Now external call goes through proxy
}

// Solution 2: Self-inject via ApplicationContext (use sparingly)
@Service
public class OrderService {
    @Lazy
    @Autowired
    private OrderService self; // Spring injects the PROXY, not 'this'

    public void createOrder() {
        self.sendEmail(); // Goes through the proxy -- @Async works
    }
}
```

**A senior engineer would note:** This same self-invocation trap applies to `@Transactional`, `@Cacheable`, `@Retryable`, and every other annotation that relies on Spring AOP proxies. If you call an annotated method from within the same class, the annotation is silently ignored. When you see mysterious "transaction not rolling back" or "cache not working" bugs, self-invocation should be the first thing you check.

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Explain the difference between JDK Dynamic Proxies and CGLIB proxies in Spring AOP. When does Spring use each, and what are the failure modes of CGLIB?">
    **Strong Answer:**

    * JDK Dynamic Proxies work by implementing the same interfaces as the target bean. The proxy is created using `java.lang.reflect.Proxy` and implements all interfaces the target class declares. Calls to interface methods are intercepted; calls to non-interface methods bypass the proxy entirely. This only works if the target bean implements at least one interface.
    * CGLIB (Code Generation Library) works by generating a subclass of the target bean at runtime using bytecode manipulation. The proxy IS-A subclass of your actual class, so it can intercept any non-final, non-private method. Since Spring Boot 2.0, CGLIB is the default even when the bean implements interfaces, to prevent `ClassCastException` when someone autowires the concrete class instead of the interface.
    * CGLIB failure mode 1: `final` classes cannot be proxied. CGLIB generates a subclass, and Java prohibits subclassing `final` classes. Marking a `@Service` as `final` with `@Transactional` methods causes a startup error. Kotlin classes are `final` by default, which is why the `kotlin-spring` compiler plugin opens annotated classes.
    * CGLIB failure mode 2: `final` methods are not intercepted. CGLIB overrides methods in the subclass, and `final` methods cannot be overridden. So `@Transactional` on a `final` method is silently ignored -- no error, no transaction. This is one of the most subtle bugs in Spring applications.
    * CGLIB failure mode 3: constructor called twice in older Spring versions. CGLIB creates a subclass requiring a superclass constructor call. Modern Spring uses Objenesis to avoid this, but on older versions, constructor side effects (logging, incrementing counters) execute twice.

    **Follow-up: Can Spring AOP intercept private methods? What about AspectJ load-time weaving -- how does it differ?**

    Spring AOP cannot intercept private methods because both JDK proxies (interface methods only) and CGLIB (cannot override private) are limited by Java's access control. AspectJ load-time weaving (LTW) modifies class bytecode at class-load time using a Java agent. Because it modifies the actual class rather than wrapping it, it can intercept private methods, static methods, field access, and object construction. The trade-off: LTW adds configuration complexity (agent setup, classloader issues), harder debugging (bytecode no longer matches source), and most Spring apps never need it. I would only reach for AspectJ LTW when advising third-party code or when self-invocation interception is architecturally required.
  </Accordion>

  <Accordion title="A developer adds @Async to a method, but it still runs synchronously. Walk me through every possible reason and how you would debug each one.">
    **Strong Answer:**

    * Reason 1 (most common): Self-invocation. The `@Async` method is called from another method in the same class via `this.asyncMethod()`. The `this` reference bypasses the proxy. Debug: check the call site. If the caller is in the same class, move the async method to a separate bean.
    * Reason 2: Missing `@EnableAsync`. Without this on a `@Configuration` class, Spring does not register the `AsyncAnnotationBeanPostProcessor`. The `@Async` annotation is inert. Debug: search your configuration classes for `@EnableAsync`.
    * Reason 3: The method is `private`. CGLIB cannot override private methods, so the proxy does not intercept the call. Spring silently ignores it. Debug: check method visibility -- it must be `public`.
    * Reason 4: No `TaskExecutor` bean configured. Spring falls back to `SimpleAsyncTaskExecutor`, which creates a new thread per invocation (no pooling). Technically it IS async, but under load it creates thousands of threads and the JVM crashes with OOM. Configure a `ThreadPoolTaskExecutor` explicitly.
    * Reason 5: With `void` return type, exceptions are swallowed by `SimpleAsyncUncaughtExceptionHandler` and only logged at WARN level. It looks like the method "did nothing."
    * Debug approach: Enable `logging.level.org.springframework.scheduling=DEBUG`. Check if the bean is a proxy: `AopUtils.isAopProxy(orderService)`. Verify the executing thread name differs from the request thread (`async-1` vs `http-nio-8080-exec-1`).

    **Follow-up: How does the SecurityContext propagate to @Async methods, and what happens if you forget to handle it?**

    By default, it does not propagate. `SecurityContextHolder` uses `ThreadLocal`, and `@Async` runs on a different thread. The async method sees an empty `SecurityContext` -- the user is anonymous. If it calls `@PreAuthorize` methods, you get `AccessDeniedException`. Fix: use `DelegatingSecurityContextExecutor` to wrap your thread pool, which copies the SecurityContext to worker threads. The same issue applies to MDC logging context -- trace IDs disappear in async threads unless you wrap the executor with `MdcTaskDecorator`.
  </Accordion>

  <Accordion title="How does Spring implement @Transactional using AOP? Trace the full proxy chain from method call to database commit.">
    **Strong Answer:**

    * At startup: `@EnableTransactionManagement` (auto-configured) registers `TransactionAttributeSourceAdvisor`. The `InfrastructureAdvisorAutoProxyCreator` BPP wraps matching beans in CGLIB proxies with `TransactionInterceptor` in their interceptor chain.
    * At invocation: external code calls `orderService.placeOrder()`. The CGLIB proxy intercepts. `TransactionInterceptor.invoke()` reads the annotation attributes via `TransactionAttributeSource` (propagation, isolation, rollbackFor, readOnly, timeout).
    * Transaction begin: the interceptor calls `PlatformTransactionManager.getTransaction(definition)`. For JPA, `JpaTransactionManager` gets a `Connection` from the `DataSource`, calls `setAutoCommit(false)`, sets isolation level, and binds the connection to the current thread via `TransactionSynchronizationManager` (a ThreadLocal registry). Downstream `@Repository` methods retrieve this thread-bound connection.
    * Method execution: `invocation.proceed()` calls your actual method. `orderRepository.save()` uses the thread-bound `EntityManager`. Hibernate queues SQL in the persistence context.
    * Commit: if no exception, Hibernate flushes (dirty checking generates SQL), SQL executes, connection commits. Connection is unbound and returned to the pool.
    * Rollback: on `RuntimeException`, the connection rolls back. On checked exception, the default is to COMMIT -- the single most surprising behavior in Spring transaction management.

    **Follow-up: What does @Transactional(readOnly = true) actually do? Is it just a hint?**

    It has real effects at three levels. JDBC: Spring calls `connection.setReadOnly(true)`, which some drivers use to route to a read replica. Hibernate: the persistence context disables dirty checking, saving CPU and memory for large result sets (loading 10,000 entities skips snapshot comparison). Database: PostgreSQL uses a lighter snapshot mode for read-only transactions. It is not a hint -- it is a compounding performance optimization. Always use it on query-only methods.
  </Accordion>

  <Accordion title="Design a cross-cutting concern using Spring AOP: implement a rate limiter that limits each endpoint to 100 requests per minute per user. What are the trade-offs vs. a Servlet Filter?">
    **Strong Answer:**

    * As an Aspect: define `@RateLimited` annotation. Create `@Around` advice matching `@annotation(RateLimited)`. Extract user from `SecurityContextHolder`, build key `rateLimit:{userId}:{method}`, INCR with EXPIRE in Redis. Either proceed or throw 429. Advantage: method-level granularity. You can rate limit expensive operations (report generation) without affecting cheap reads. Access to method arguments enables per-resource limiting.
    * As a Filter: intercept at HTTP level before Spring MVC processing. Advantage: rejected requests consume minimal resources. Catches all requests including non-Spring endpoints.
    * Trade-off: Filters are better for broad URL-based protection (global DDoS defense). Aspects are better for fine-grained business-logic-aware limiting. In practice, use both: filter for global limits (1000 req/min), aspect for specific operations (5 reports/hour).
    * Hidden gap: AOP aspects only intercept Spring-managed bean calls. Calls via reflection, from `@Scheduled`, or within the same class bypass the aspect. Filters have no such gap for HTTP traffic.

    **Follow-up: How do you handle it if your Redis instance is down? Fail open or fail closed?**

    Context-dependent. For a public API, fail open -- a Redis outage should not become an API outage. For financial APIs preventing brute-force attacks, fail closed. Implement with try-catch around the Redis call, controlled by a config flag (`rateLimit.failOpen=true`). Log at ERROR and alert. Consider a local in-memory fallback (Guava `RateLimiter`) for degraded-but-functional rate limiting when Redis is unavailable.
  </Accordion>
</AccordionGroup>
