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.
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.servicepackage”). 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
@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.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.
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):
@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
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?
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?
- JDK Dynamic Proxies work by implementing the same interfaces as the target bean. The proxy is created using
java.lang.reflect.Proxyand 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
ClassCastExceptionwhen someone autowires the concrete class instead of the interface. - CGLIB failure mode 1:
finalclasses cannot be proxied. CGLIB generates a subclass, and Java prohibits subclassingfinalclasses. Marking a@Serviceasfinalwith@Transactionalmethods causes a startup error. Kotlin classes arefinalby default, which is why thekotlin-springcompiler plugin opens annotated classes. - CGLIB failure mode 2:
finalmethods are not intercepted. CGLIB overrides methods in the subclass, andfinalmethods cannot be overridden. So@Transactionalon afinalmethod 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.
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.
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.
- Reason 1 (most common): Self-invocation. The
@Asyncmethod is called from another method in the same class viathis.asyncMethod(). Thethisreference 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@Configurationclass, Spring does not register theAsyncAnnotationBeanPostProcessor. The@Asyncannotation 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 bepublic. - Reason 4: No
TaskExecutorbean configured. Spring falls back toSimpleAsyncTaskExecutor, 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 aThreadPoolTaskExecutorexplicitly. - Reason 5: With
voidreturn type, exceptions are swallowed bySimpleAsyncUncaughtExceptionHandlerand 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-1vshttp-nio-8080-exec-1).
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.How does Spring implement @Transactional using AOP? Trace the full proxy chain from method call to database commit.
How does Spring implement @Transactional using AOP? Trace the full proxy chain from method call to database commit.
- At startup:
@EnableTransactionManagement(auto-configured) registersTransactionAttributeSourceAdvisor. TheInfrastructureAdvisorAutoProxyCreatorBPP wraps matching beans in CGLIB proxies withTransactionInterceptorin their interceptor chain. - At invocation: external code calls
orderService.placeOrder(). The CGLIB proxy intercepts.TransactionInterceptor.invoke()reads the annotation attributes viaTransactionAttributeSource(propagation, isolation, rollbackFor, readOnly, timeout). - Transaction begin: the interceptor calls
PlatformTransactionManager.getTransaction(definition). For JPA,JpaTransactionManagergets aConnectionfrom theDataSource, callssetAutoCommit(false), sets isolation level, and binds the connection to the current thread viaTransactionSynchronizationManager(a ThreadLocal registry). Downstream@Repositorymethods retrieve this thread-bound connection. - Method execution:
invocation.proceed()calls your actual method.orderRepository.save()uses the thread-boundEntityManager. 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.
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.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?
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?
- As an Aspect: define
@RateLimitedannotation. Create@Aroundadvice matching@annotation(RateLimited). Extract user fromSecurityContextHolder, build keyrateLimit:{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.
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.