返回 Skill 列表
extension
分类: 开发与工程无需 API Key

spring-aop

Spring AOP(面向切面编程)基于Spring Boot 3.x。涵盖@Aspect、切入点表达式、@Around/@Before/@After通知、自定义注解、日志记录、安全、缓存以及事务模式。用于处理横切关注点。使用场景:用户提到“aspect”、“AOP”、“横切”、“日志切面”、“切入点”,或者询问如何拦截方法、对多个方法应用逻辑、@Around通知等。不应用于:安全配置 - 应该使用`spring-security`,事务管理 - 应该使用`spring-data-jpa`。

person作者: jakexiaohubgithub

Spring AOP

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

| Error | Cause | Solution | |-------|-------|----------| | Aspect not applied | Bean not Spring-managed | Use @Component or @Bean | | Self-invocation | Internal call bypasses proxy | Use self-injection | | Wrong order | Aspects not ordered | Use @Order | | Performance | Aspect too broad | Narrow pointcut |

Quick Troubleshooting

| Issue | Possible Cause | Solution | |-------|---------------|----------| | Aspect not executing | Bean not Spring-managed | Ensure class has @Component | | Self-invocation doesn't trigger | Internal call bypasses proxy | Use self-injection | | Wrong execution order | Missing @Order | Add @Order with priority | | Performance degradation | Pointcut too broad | Narrow scope, use custom annotations |

Reference Documentation