关于 spring-aop
面向 Spring 开发者的 AOP 参考技能,介绍切点、通知类型、自定义注解和切面排序,并列出实践建议与常见陷阱。
主要功能
- 01方法与注解切点示例
- 02多种通知类型示例
- 03自定义注解设计
- 04切面执行顺序控制
- 05常见问题与实践建议
适用场景
- 01为服务层方法添加日志
- 02为注解标记的方法应用切面
- 03控制安全与其他切面的顺序
- 04诊断自调用导致的切面失效
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill spring-aop安装后在 Claude Code 中输入/spring-aop确认已加载。
附带文件patterns.md
详细介绍
Spring AOP
完整参考:有关性能监控、重试逻辑、审计日志、安全检查、速率限制和测试模式,请参阅 patterns.md。
快速开始
@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;
}
}
切入点表达式
方法执行
@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() {}
}
基于注解
@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();
}
}
组合
@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() {}
}
通知类型
@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());
}
}
自定义注解
@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
@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 { }
最佳实践
- 使用具体的切入点(不要范围过广)
- 使用 @Order 对切面排序
- 使用自定义注解,使代码更清晰
- 避免在切面中编写复杂逻辑
- 监控性能影响
- 单独测试切面
- 不要静默捕获异常
- 不要修改共享状态
- 不要将其用于核心业务逻辑
常见陷阱
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 切面未生效 | Bean 未由 Spring 管理 | 使用 @Component 或 @Bean |
| 自调用 | 内部调用绕过代理 | 使用自身注入 |
| 顺序错误 | 切面未排序 | 使用 @Order |
| 性能问题 | 切面范围过广 | 缩小切入点范围 |
快速故障排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 切面未执行 | Bean 未由 Spring 管理 | 确保类带有 @Component |
| 自调用未触发 | 内部调用绕过代理 | 使用自身注入 |
| 执行顺序错误 | 缺少 @Order | 添加带优先级的 @Order |
| 性能下降 | 切入点范围过广 | 缩小范围,使用自定义注解 |
参考文档
评论
支持 Markdown