Spring AOP

SkillMonitoring & ops

Spring AOP (Aspect-Oriented Programming) for Spring Boot 3.x. Covers @Aspect, pointcut expressions, @Around/@Before/@After advice, custom annotations, logging, security, caching, and transaction patterns. Use for cross-cutting concerns.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Spring AOP skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/spring-aop/SKILL.md and read by ahel’s review.

Full Reference: See patterns.md for Performance Monitoring, Retry Logic, Audit Logging, Security Check, Rate Limiting, and Testing patterns.

Quick Start

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {}

@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        String method = joinPoint.getSignature().toShortString();
        log.info("Executing: {}", method);

        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - start;

        log.info("Completed: {} in {} ms", method, duration);
        return result;
    }
}

Pointcut Expressions

Method Execution

@Aspect
@Component
public class PointcutExamples {

    // All methods in a package
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    // Public methods
    @Pointcut("execution(public * *(..))")
    public void publicMethods() {}

    // Methods starting with "get"
    @Pointcut("execution(* get*(..))")
    public void getters() {}

    // Package and subpackages
    @Pointcut("execution(* com.example..*.*(..))")
    public void entireApplication() {}
}

Annotation-Based

@Aspect
@Component
public class AnnotationPointcuts {

    // Methods annotated with @Transactional
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}

    // Classes annotated with @Service
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void serviceBeans() {}

    // With annotation binding
    @Around("@annotation(loggable)")
    public Object logWithAnnotation(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
        String value = loggable.value();
        return pjp.proceed();
    }
}

Combinations

@Aspect
@Component
public class CombinedPointcuts {

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

    @Pointcut("execution(* com.example.repository.*.*(..))")
    public void repositoryLayer() {}

    // AND
    @Pointcut("serviceLayer() && publicMethods()")
    public void publicServiceMethods() {}

    // OR
    @Pointcut("serviceLayer() || repositoryLayer()")
    public void dataLayer() {}

    // NOT
    @Pointcut("serviceLayer() && !execution(* get*(..))")
    public void nonGetterServiceMethods() {}
}

Advice Types

@Around

@Aspect
@Component
public class AroundAdvice {

    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        log.info("Before: {} with args: {}", methodName, Arrays.toString(args));

        try {
            Object result = joinPoint.proceed();
            log.info("After returning: {} returned: {}", methodName, result);
            return result;
        } catch (Exception e) {
            log.error("After throwing: {} threw: {}", methodName, e.getMessage());
            throw e;
        }
    }

    // Modify arguments
    @Around("execution(* com.example.service.UserService.createUser(..))")
    public Object sanitizeInput(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof String str) {
                args[i] = sanitize(str);
            }
        }
        return joinPoint.proceed(args);
    }
}

@Before, @After, @AfterReturning, @AfterThrowing

@Aspect
@Component
public class OtherAdvice {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        log.info("Entering: {}", joinPoint.getSignature().toShortString());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        log.info("Completed: {}", joinPoint.getSignature().toShortString());
    }

    @AfterReturning(pointcut = "execution(* com.example.service.UserService.createUser(..))", returning = "user")
    public void logCreatedUser(JoinPoint joinPoint, User user) {
        log.info("Created user: {}", user.getId());
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        log.error("Exception in {}: {}", joinPoint.getSignature().toShortString(), ex.getMessage());
    }
}

Custom Annotations

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
    String value() default "";
    boolean logArgs() default true;
    boolean logResult() default true;
    LogLevel level() default LogLevel.INFO;
}

@Aspect
@Component
@Slf4j
public class LoggableAspect {

    @Around("@annotation(loggable)")
    public Object logMethod(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        String message = loggable.value().isEmpty() ? methodName : loggable.value();

        if (loggable.logArgs()) {
            log.info("Entering {} with args: {}", message, Arrays.toString(joinPoint.getArgs()));
        }

        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();

        if (loggable.logResult()) {
            log.info("Exiting {} with result: {} ({}ms)", message, result, System.currentTimeMillis() - start);
        }
        return result;
    }
}

// Usage
@Service
public class UserService {
    @Loggable(value = "Creating user", logResult = false)
    public User createUser(CreateUserRequest request) {
        return userRepository.save(new User(request));
    }
}

Aspect Ordering

@Aspect
@Component
@Order(1)  // Executes first
public class SecurityAspect { }

@Aspect
@Component
@Order(2)  // Executes second
public class LoggingAspect { }

@Aspect
@Component
@Order(3)  // Executes third
public class PerformanceAspect { }

Best Practices

  • Use specific pointcuts (not too broad)
  • Order aspects with @Order
  • Use custom annotations for clarity
  • Avoid complex logic in aspects
  • Monitor performance impact
  • Test aspects in isolation
  • Don't catch exceptions silently
  • Don't modify shared state
  • Don't use for core business logic

Common Pitfalls

ErrorCauseSolution
Aspect not appliedBean not Spring-managedUse @Component or @Bean
Self-invocationInternal call bypasses proxyUse self-injection
Wrong orderAspects not orderedUse @Order
PerformanceAspect too broadNarrow pointcut

Quick Troubleshooting

IssuePossible CauseSolution
Aspect not executingBean not Spring-managedEnsure class has @Component
Self-invocation doesn't triggerInternal call bypasses proxyUse self-injection
Wrong execution orderMissing @OrderAdd @Order with priority
Performance degradationPointcut too broadNarrow scope, use custom annotations

Reference Documentation

Signals

GitHub stars
33
Forks
8
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
spring-aop
Source
github.com/claude-dev-suite/claude-dev-suite