Implementing Cross-Cutting Concerns with Spring AOP

Core Terminology

  • Aspect: A modularized concern (e.g., audit logging) implemented via @Aspect-annotated classes or XML configuration.
  • Join Point: A specific point in execution—most commonly a method invocation—where advice can be applied.
  • Advice: Code executed at a join point. Spring supports @Before, @AfterReturning, @AfterThrowing, @After, and @Around.
  • Pointcut: An expression that selects matching join points. Advice is bound to pointcuts to define *where* it runs.
  • Introduction: Enables adding new interfaces and implementations to existing beans—useful for mixin-style behavior injection.
  • Target Object: The original object being advised; in Spring, this is typically wrapped by a dynamic proxy.
  • AOP Proxy: Either a JDK interface-based proxy or a CGLIB class-based proxy generated at runtime.
  • Weaving: The process of linking aspects in to target objects to produce advised proxies.

Getting Started

1. Enable Aspect Support

Activate annotation-driven AOP either through Java config or XML:

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
    // Configuration beans...
}

Or in XML:

<aop:aspectj-autoproxy/>

2. Define an Aspect

Create a class annotated with @Aspect. It behaves like a regular Spring-managed bean but gains special AOP capabilities:

@Aspect
@Component
public class LoggingAspect {
    // Fields, methods, pointcuts, and advice go here
}

In XML, register it as a standard bean:

<bean id="loggingAspect" class="com.example.LoggingAspect"/>

3. Express Join Points with Pointcuts

A pointcut declaration consists of two parts: a signature (a void method) and an expression (using AspectJ syntax). For example:

@Pointcut("execution(* com.example.account.*Service.*(..))")
public void serviceLayerOperations() {}
Supported Pointcut Designators (PCDs)
  • execution(): Matches method executions (most widely used).
  • within(): Restricts matching to types within specified packages.
  • this() / target(): Filters based on proxy or target object type.
  • @annotation(): Selects methods marked with a given annotation.
  • @within(): Matches types annotated with a specific annotation.
  • args(): Matches calls where arguments match declared types.
  • bean(): Spring-specific PCD matching beans by name pattern (e.g., bean(*Repository)).
Composing Pointcuts

Combine expressions using &&, ||, and !:

@Pointcut("execution(* com.example.web.*Controller.*(..))")
public void webControllers() {}

@Pointcut("execution(* com.example.service.*Service.*(..))")
public void serviceMethods() {}

@Pointcut("webControllers() || serviceMethods()")
public void controllerOrService() {}
Common Pointcut Patterns
// All public methods in any package under 'com.example'
@Pointcut("execution(public * *(..))")

// Any setter method
@Pointcut("execution(* set*(..))")

// Methods in repository interfaces or implementations
@Pointcut("within(com.example.repository..*)")

// Beans whose ID ends in 'Service'
@Pointcut("bean(*Service)")

// Methods annotated with @Secured
@Pointcut("@annotation(org.springframework.security.access.annotation.Secured)")

4. Attach Advice to Pointcuts

Before Advice

Executes before the matched join point:

@Before("serviceLayerOperations()")
public void logEntry() {
    System.out.println("Entering service method...");
}
After Returning Advice

Runs only if the method completes successfully. Optionally captures return value:

@AfterReturning(
    pointcut = "serviceLayerOperations()",
    returning = "result"
)
public void logSuccess(Object result) {
    System.out.println("Method succeeded with result: " + result);
}
After Throwing Advice

Triggers when an exception is thrown:

@AfterThrowing(
    pointcut = "serviceLayerOperations()",
    throwing = "error"
)
public void handleFailure(RuntimeException error) {
    System.err.println("Operation failed: " + error.getMessage());
}
After (Finally) Advice

Always executes—ideal for cleanup:

@After("serviceLayerOperations()")
public void releaseResources() {
    System.out.println("Releasing resources...");
}
Around Advice

Most powerful advice type—it wraps the join point and controls whether and how it proceeds:

@Around("serviceLayerOperations()")
public Object profileExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.nanoTime();
    try {
        Object result = joinPoint.proceed(); // Proceeds to actual method
        long duration = System.nanoTime() - start;
        System.out.printf("Executed in %.2f ms%n", duration / 1_000_000.0);
        return result;
    } catch (Throwable t) {
        long duration = System.nanoTime() - start;
        System.err.printf("Failed after %.2f ms: %s%n", duration / 1_000_000.0, t.getMessage());
        throw t;
    }
}

5. Introduce New Capabilities with Introduction

Add interfaces and default implementations to existing beans using @DeclareParents:

@Aspect
@Component
public class MonitoringAspect {

    @DeclareParents(
        value = "com.example.service.*+",
        defaultImpl = DefaultMetricsCollector.class
    )
    public static MetricsCollector metricsCollector;

    @Before("serviceLayerOperations() && this(metricsCollector)")
    public void trackInvocation(MetricsCollector collector) {
        collector.recordInvocation();
    }
}

In this example, all service beans automatically gain MetricsCollector functionality without modifying their source code.

Tags: spring-aop aspectj java proxy-pattern cross-cutting-concerns

Posted on Thu, 05 Nov 2026 16:50:48 +0000 by Verminox