Return to site

🪄🌱 SPRING BOOT AOP: CENTRALIZE CROSS-CUTTING CONCERNS WITHOUT POLLUTING BUSINESS CODE

· spring

Logging, auditing, performance monitoring… these concerns often appear across many services.

With Aspect-Oriented Programming (AOP), Spring Boot lets you apply them around method executions without duplicating the same technical code everywhere. 🔄

Section image

🔸 TL;DR

Spring AOP intercepts method executions through proxies and applies reusable technical behaviors before, after, or around them.

It is especially useful for logging, auditing, metrics, tracing, transactions, caching and security. 🧩

Start by adding:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

🔸 1️⃣ MEASURE METHOD EXECUTION TIME

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {
}
@Aspect
@Component
public class PerformanceAspect {

    @Around("@annotation(TrackTime)")
    public Object measure(ProceedingJoinPoint joinPoint)
            throws Throwable {

        long start = System.nanoTime();

        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.nanoTime() - start;

            System.out.printf(
                "%s executed in %d ms%n",
                joinPoint.getSignature(),
                duration / 1_000_000
            );
        }
    }
}

Add @TrackTime to selected methods to measure their execution time without inserting timing logic into the business service. ⏱️

🔸 2️⃣ AUDIT SUCCESSFUL BUSINESS OPERATIONS

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audited {
    String action();
}
@Aspect
@Component
public class AuditAspect {

    @AfterReturning(
        pointcut = "@annotation(audited)",
        returning = "result"
    )
    public void audit(
            JoinPoint joinPoint,
            Audited audited,
            Object result) {

        System.out.printf(
            "Action=%s Method=%s Result=%s%n",
            audited.action(),
            joinPoint.getSignature().getName(),
            result
        );
    }

This aspect records successful operations marked with @Audited, keeping audit concerns separate from business rules. 🧾

🔸 3️⃣ CENTRALIZE EXCEPTION LOGGING

@Aspect
@Component
public class ExceptionLoggingAspect {

    private static final Logger LOGGER =
        LoggerFactory.getLogger(
            ExceptionLoggingAspect.class
        );

    @AfterThrowing(
        pointcut = "within(@org.springframework.stereotype.Service *)",
        throwing = "exception"
    )
    public void logFailure(
            JoinPoint joinPoint,
            Throwable exception) {

        LOGGER.error(
            "Failure in {}",
            joinPoint.getSignature(),
            exception
        );
    }
}

@AfterThrowing logs exceptions raised by Spring services while avoiding repetitive try/catch blocks used only for logging. 🚨

🔸 TAKEAWAYS

▪️ Keep cross-cutting technical concerns outside business services.

▪️ Prefer explicit custom annotations over broad, difficult-to-understand pointcuts.

▪️ Use @Around only when you need control before and after execution.

▪️ Remember that Spring AOP is proxy-based: internal self-invocation may bypass the aspect.

▪️ Do not hide core business logic inside aspects—AOP should improve clarity, not create invisible behavior.

▪️ Test aspects independently and verify that their pointcuts target only the intended methods.

AOP is powerful, but the goal is not to intercept everything. The goal is to remove duplication while keeping application behavior understandable. 🎯

#Java #SpringBoot #SpringFramework #SpringAOP #AOP #SoftwareArchitecture #CleanCode #BackendDevelopment #JavaDeveloper #Programming

Go further with Java certification:

Java👇

Spring👇

SpringBook👇

JavaBook👇