Skip to main content
Advertisement

4.1 ~ 4.4 AOP (Aspect-Oriented Programming)

1. Why AOP? — Separating Cross-cutting Concerns

While writing clean code, you quickly encounter a frustrating problem: boilerplate code like logging, execution-time measurement, and transaction management keeps repeating across every method.

public class OrderService {

public void placeOrder(String item) {
long start = System.currentTimeMillis(); // Common code ①
System.out.println("[LOG] placeOrder started"); // Common code ②

// ✅ Core business logic (just one line)
System.out.println(item + " order placed!");

long end = System.currentTimeMillis(); // Common code ①
System.out.println("[LOG] Execution time: " + (end - start) + "ms"); // Common code ②
}

public void cancelOrder(String item) {
long start = System.currentTimeMillis(); // Common code ①
System.out.println("[LOG] cancelOrder started"); // Common code ②

// ✅ Core business logic (just one line)
System.out.println(item + " order cancelled!");

long end = System.currentTimeMillis(); // Common code ①
System.out.println("[LOG] Execution time: " + (end - start) + "ms"); // Common code ②
}
}

The actual business logic is just one line, but the repeated boilerplate bloats every method. With 100 methods, you'd need to update 100 places for a single change.

AOP (Aspect-Oriented Programming) addresses this by completely separating Core Concerns(business logic) from ** Cross-cutting Concerns**(logging, transactions, etc.) that repeatedly appear alongside them.


2. AOP Key Terminology

TermDescriptionExample
AspectA module encapsulating a cross-cutting concernAn @Aspect class
TargetThe actual business object where AOP is appliedOrderService
AdviceThe actual shared code to be executed (the "what")Logging, time measurement
PointcutAn expression that defines where Advice is applied (the "where")execution(* com.example..*(..))
JoinPointA point in program execution where Advice can be appliedThe moment placeOrder() is called
WeavingThe process of linking an Aspect to a TargetSpring creating a proxy

Types of Advice (the "when")

TypeDescription
@BeforeRuns before the method executes
@AfterReturningRuns after the method returns normally
@AfterThrowingRuns after the method throws an exception
@AfterRuns regardless of outcome (like finally)
@AroundControls both before and after the method (most powerful)
@AfterReturningRuns after the method returns successfully to process the result
@AfterThrowingRuns after an exception is thrown to handle error info

Pointcut Expression Guide

How do you write a pointcut like execution(* com.example.service.*.*(..))?

  • *: Matches any return type / method name / package path
  • ..: Matches zero or more parameters / all sub-packages
  • (..): Matches any number of parameters
  • execution: Matches method execution join points
  • @annotation: Matches only methods with a specific annotation

3. Spring AOP in Practice (Execution Time Tracking)

With AOP, we can completely remove boilerplate from business logic.

Step 1: Add the dependency

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-aop'

Step 2: Write the Aspect class

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect // Declares this class as an AOP Aspect
@Component // Registers it as a Spring Bean
public class TimeTraceAop {

// Pointcut: apply to all methods in com.example and subpackages
@Around("execution(* com.example..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("[AOP] START: " + joinPoint.toString());

try {
return joinPoint.proceed(); // ✅ Calls the actual business method
} finally {
long finish = System.currentTimeMillis();
System.out.println("[AOP] END: " + joinPoint.toString() + " " + (finish - start) + "ms");
}
}
}

Step 3: Keep business logic pure

@Service
public class OrderService {

public void placeOrder(String item) {
// ✅ Only core logic remains!
System.out.println(item + " order placed!");
}

public void cancelOrder(String item) {
// ✅ Clean and focused on its single responsibility!
System.out.println(item + " order cancelled!");
}
}

TimeTraceAop monitors all methods and measures time automatically, so OrderService can focus purely on its own responsibility — handling orders.


4. Proxy Pattern: How It Works Under the Hood

You might wonder how AOP works its magic. Spring AOP is built on the Runtime Dynamic Proxy pattern.

Client Code              Spring-injected Proxy Object            Real Object
OrderController --> OrderService (Proxy) [AOP code inside] --> OrderService (Real)
  1. When Spring detects @Aspect, it does not inject the real OrderService directly. Instead, it injects a proxy object that wraps the real one.
  2. The client calls methods on the proxy, unaware it is not the real class.
  3. The proxy executes the Advice (shared code) first, then delegates to the real object's method.

Summary: AOP is an elegant technique that transparently injects cross-cutting behavior without ever touching the business code. Spring's @Transactional annotation is itself implemented using this exact AOP proxy mechanism.

5. Practical Tip: Combining Custom Annotations with AOP

When you want to apply AOP selectively to specific methods rather than an entire package, use Custom Annotations.

Step 1: Create the annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime { } // Used simply as a marker

Step 2: Detect the annotation in the Aspect

@Around("@annotation(LogExecutionTime)") // Only for methods with this annotation!
public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;

System.out.println(joinPoint.getSignature() + " executed in: " + executionTime + "ms");
return proceed;
}

Step 3: Apply to the desired method

@LogExecutionTime
public void heavyJob() {
// Perform logic...
}
Advertisement