Spring AOP adds behavior such as timing, logging, or access checks around method calls to Spring-managed beans. It does this through a proxy that intercepts a call before passing it to the target object. The most useful interview answer includes that boundary, because it explains both when advice runs and why a call within the same object can bypass it.
1. What Does Spring AOP Do?
Consider an order service that places orders. Measuring how long each call takes is useful, but the timing code does not belong to the order placement rule. Timing is a cross-cutting concern because the same behavior may apply to many service methods.
An aspect holds that shared behavior and selects where to apply it. The service keeps its business method, while Spring routes matching calls through a proxy that runs the timing code. Spring AOP works at the execution of methods on Spring-managed beans. It does not intercept arbitrary object creation or field access.
The call path keeps the timing concern outside the order method.
2. What Are an Aspect, a Join Point, a Pointcut and Advice?
The terms describe different parts of one call. A join point in Spring AOP is a method execution that can be advised. A pointcut is an expression that selects join points. Advice is the action performed when a selected call reaches the proxy. The aspect groups the pointcut and advice. The target is the object whose method ultimately runs.
The pointcut in the working example selects executions of OrderService.place(..).
@Around("execution(* com.howtodoinjava.aop.OrderService.place(..))")
Here, @Around identifies the advice type and the execution expression is the pointcut. When another bean calls place() through its Spring proxy, the advice runs before and after the target method. The complete aspect appears with the application code below.
3. What Are the Five Types of Advice?
Choose the advice type according to the behavior needed. A log before a call does not need control over the method’s return value, while a timer needs to measure both before and after execution.
| Advice | When it runs | Typical use |
|---|---|---|
| @Before | Before the target method | Record an attempted operation |
| @AfterReturning | After a normal return | Inspect a successful result |
| @AfterThrowing | After an exception | Record a failed operation |
| @After | After the method finishes, including exceptional completion | Release or report after a call |
| @Around | Around the invocation | Measure duration or decide whether to proceed |
@Around advice receives a ProceedingJoinPoint. Calling proceed() continues the invocation and returns its result. If the advice does not call proceed(), the target method does not run. That power is useful for timing, but simpler advice is easier to use when only one side of the call matters.
The diagram places each advice type relative to the target method. An exception follows the throwing branch instead of the normal-return branch.
4. How Do We Measure a Method Call with Spring AOP?
The following small application places an order and measures its place() call. Add spring-boot-starter-aspectj to the Maven project. Spring Boot configures Spring AOP when AspectJ is on the classpath, so this example needs no explicit @EnableAspectJAutoProxy annotation.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>
The service has one business method and a batch method that calls it. The internal call will help expose the proxy boundary after the normal call works.
package com.howtodoinjava.aop;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public String place(String id) {
return "placed " + id;
}
public String placeBatch(String id) {
return place(id);
}
}
The aspect measures elapsed time around place(). Its finally block records a completed timing attempt even if the method throws. It returns the original result from proceed() without changing the service’s behavior.
package com.howtodoinjava.aop;
import java.util.concurrent.atomic.AtomicInteger;
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 TimingAspect {
private final AtomicInteger timedCalls = new AtomicInteger();
private volatile long lastDurationNanos = -1;
@Around("execution(* com.howtodoinjava.aop.OrderService.place(..))")
public Object timePlace(ProceedingJoinPoint call) throws Throwable {
long started = System.nanoTime();
try {
return call.proceed();
} finally {
lastDurationNanos = System.nanoTime() - started;
timedCalls.incrementAndGet();
}
}
public int timedCalls() { return timedCalls.get(); }
public long lastDurationNanos() { return lastDurationNanos; }
}
In the execution expression, * accepts any return type, the fully qualified name selects OrderService.place, and (..) accepts any argument list. The expression selects executions of place(); it does not select placeBatch().
The application asks Spring for the service and aspect, makes two calls, and prints what the aspect observed.
package com.howtodoinjava.aop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class AopInterviewApplication {
public static void main(String[] args) {
SpringApplication.run(AopInterviewApplication.class, args);
}
@Bean
CommandLineRunner run(OrderService orders, TimingAspect timing) {
return args -> {
System.out.println(orders.place("A-1"));
System.out.println(orders.placeBatch("B-1"));
System.out.println("timed calls " + timing.timedCalls());
System.out.println("duration recorded " + (timing.lastDurationNanos() >= 0));
};
}
}
Running the application prints the following lines after Spring startup. The duration itself varies, so the example checks that the aspect recorded one.
placed A-1
placed B-1
timed calls 1
duration recorded true
Both orders were placed, but the aspect counted only one call. The next section explains why the batch call did not add a second timing record.
5. Why Does Self-Invocation Bypass the Advice?
The runner calls orders.place(“A-1”) through the injected Spring proxy. The pointcut matches, so timePlace() runs once. The runner then calls orders.placeBatch(“B-1”). That method reaches the target object, where its call to place(id) stays within the same object. The internal call does not go back through the proxy, so the timing advice does not run again.
This is self-invocation, and it is a common reason an aspect appears to work for one call but not another. The Spring proxying reference documents this boundary. If the batch path must time each order placement, move the advised operation to a separate Spring bean and call that bean from the batch service. The call then crosses the proxy boundary.
6. Which Methods Can a Spring AOP Proxy Advise?
Spring AOP can use a JDK dynamic proxy for an interface or a class-based proxy for the target class. Spring Boot uses class-based proxies by default. Setting spring.aop.proxy-target-class=false requests interface-based proxies when an interface is available. The proxy choice changes what type callers can use, but it does not remove the self-invocation rule.
The proxy must expose a method that a caller can reach. This diagram shows the two proxy choices and the methods that remain outside the class-based proxy’s advice boundary.
A method must be reachable through the proxy to be advised. Private methods cannot be advised by Spring AOP, and final methods cannot be overridden by a class-based proxy. In addition, constructing an object directly with new leaves it outside Spring’s bean and proxy lifecycle. For a missing advice call, first check that Spring created the target bean, the pointcut matches its method, and the caller invokes it through the proxy.
7. How Does Spring AOP Differ from AspectJ Weaving?
Spring AOP is a good fit for behavior around Spring bean methods. AspectJ weaving can advise a wider range of join points, including constructors and field access. The two tools therefore have different boundaries. Adding AspectJ annotations to a Spring AOP aspect does not make a private method or an internal self-call pass through a Spring proxy.
The difference is the set of join points available for advice.
8. What Happens When Multiple Aspects Advise the Same Method?
When several aspects advise the same method, use @Order if their sequence affects behavior. The aspect with the lower order value runs first on entry and last on exit. Two aspects with the same order value have no guaranteed relative order, so dependent behavior should use distinct values. For independent logging and timing, avoid relying on an incidental order.
With order values 1 and 2, the advice nests around the target call as shown below. The method returns through the aspects in reverse order.
Happy Learning!
Once I was asked what are the do’s and don’ts of AOP? … I don’t have the answer yet..
I will find time to get an answer for you.
Thanks, nice share.