package com.example.demo.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// Pointcut for all methods in service package
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void serviceMethods() {}
// Pointcut for methods annotated with @Loggable
@Pointcut("@annotation(com.example.demo.annotation.Loggable)")
public void loggableMethods() {}
// Before advice
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
logger.info("Calling method: {} with args: {}",
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}
// After returning advice
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("Method {} returned: {}",
joinPoint.getSignature().getName(),
result);
}
// After throwing advice
@AfterThrowing(pointcut = "serviceMethods()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
logger.error("Method {} threw exception: {}",
joinPoint.getSignature().getName(),
error.getMessage());
}
// Around advice for performance monitoring
@Around("loggableMethods()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
logger.info("{} executed in {} ms",
joinPoint.getSignature().toShortString(),
executionTime);
return result;
} catch (Throwable throwable) {
long executionTime = System.currentTimeMillis() - start;
logger.error("{} threw exception after {} ms",
joinPoint.getSignature().toShortString(),
executionTime);
throw throwable;
}
}
}
package com.example.demo.aspect;
import com.example.demo.annotation.Auditable;
import com.example.demo.service.AuditService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
public class AuditAspect {
private final AuditService auditService;
public AuditAspect(AuditService auditService) {
this.auditService = auditService;
}
@Around("@annotation(com.example.demo.annotation.Auditable)")
public Object auditMethod(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Auditable auditable = method.getAnnotation(Auditable.class);
String action = auditable.action();
String entityType = auditable.entityType();
LocalDateTime startTime = LocalDateTime.now();
Object result = null;
boolean success = true;
String error = null;
try {
result = joinPoint.proceed();
return result;
} catch (Throwable throwable) {
success = false;
error = throwable.getMessage();
throw throwable;
} finally {
auditService.logAudit(
action,
entityType,
startTime,
success,
error
);
}
}
}
package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
String action();
String entityType();
}
// Usage:
// @Loggable
// public User findById(Long id) { ... }
//
// @Auditable(action = "CREATE", entityType = "USER")
// public User createUser(User user) { ... }
AOP separates cross-cutting concerns from business logic. Aspects modularize logging, security, transactions, caching. @Aspect defines aspect classes. @Before, @After, @Around specify advice timing. Pointcuts select join points using expressions. @Around advice controls method execution fully. I use AOP for method timing, audit logging, exception handling. Aspects reduce code duplication across services. Spring AOP uses proxies; AspectJ uses bytecode weaving for more power. Proper pointcut design prevents performance issues. AOP enables declarative programming—annotations trigger cross-cutting behavior. Understanding AOP improves code organization and maintains separation of concerns. It's powerful but should be used judiciously to avoid obscuring program flow.