AOP

AOP(Aspect-Oriented Programming,面向切面编程)是对 OOP 的补充,用于将横切关注点(日志、事务、安全等)从业务逻辑中剥离,统一管理。

核心概念

概念说明
Aspect(切面)横切关注点的模块化,包含 Pointcut + Advice
Join Point(连接点)程序执行的某个点,Spring AOP 中特指方法执行
Pointcut(切点)匹配连接点的表达式,决定切面在哪里织入
Advice(通知)切面在连接点执行的动作(前置、后置、环绕等)
Target(目标对象)被切面增强的对象
Proxy(代理)Spring AOP 为目标对象创建的代理(JDK 动态代理或 CGLIB)
Weaving(织入)将切面应用到目标对象并创建代理的过程

Spring AOP vs AspectJ

对比Spring AOPAspectJ
织入时机运行时(代理)编译时 / 加载时
支持范围仅 Spring Bean 的方法执行任意 Java 代码
性能略低(代理开销)更高
使用复杂度简单复杂
适用场景常规业务场景高性能或非 Spring 环境

Spring AOP 默认使用 AspectJ 的注解语法,但底层织入是代理方式。

快速使用

引入依赖:

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

五种 Advice 类型

@Aspect
@Component
public class LogAspect {
 
    // 切点表达式复用
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
 
    // 方法执行前
    @Before("serviceLayer()")
    public void before(JoinPoint jp) {
        System.out.println("Before: " + jp.getSignature().getName());
    }
 
    // 方法正常返回后
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        System.out.println("Return: " + result);
    }
 
    // 方法抛出异常后
    @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
    public void afterThrowing(JoinPoint jp, Exception ex) {
        System.out.println("Exception: " + ex.getMessage());
    }
 
    // 无论正常或异常都执行(不能获取返回值)
    @After("serviceLayer()")
    public void after(JoinPoint jp) {
        System.out.println("After: " + jp.getSignature().getName());
    }
 
    // 环绕通知,功能最强,可控制是否执行目标方法
    @Around("serviceLayer()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // 执行目标方法
        long cost = System.currentTimeMillis() - start;
        System.out.println("耗时: " + cost + "ms");
        return result;
    }
}

执行顺序(正常):@Around前@Before → 目标方法 → @AfterReturning@After@Around后

执行顺序(异常):@Around前@Before → 目标方法抛异常 → @AfterThrowing@After

Pointcut 表达式

execution 表达式

execution(修饰符? 返回类型 类路径? 方法名(参数) 异常?)
示例说明
execution(* com.example.service.*.*(..))service 包下所有类的所有方法
execution(* com.example..*.*(..))example 包及子包下所有方法
execution(public * *(..))所有 public 方法
execution(* *Service.*(..))类名以 Service 结尾的所有方法
execution(* com.example.UserService.get*(..))UserService 中 get 开头的方法

其他切点指示符

// 注解匹配:标注了 @Log 的方法
@Pointcut("@annotation(com.example.annotation.Log)")
public void logAnnotation() {}
 
// Bean 名称匹配
@Pointcut("bean(userService)")
public void userServiceBean() {}
 
// 参数类型匹配
@Pointcut("args(java.lang.String)")
public void stringArgs() {}
 
// 组合使用
@Pointcut("serviceLayer() && logAnnotation()")
public void serviceWithLog() {}

获取注解参数

@Around("@annotation(log)")
public Object aroundLog(ProceedingJoinPoint pjp, Log log) throws Throwable {
    System.out.println("操作描述: " + log.value());
    return pjp.proceed();
}

常见使用场景

操作日志

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperLog {
    String value() default "";
}
 
@Aspect
@Component
public class OperLogAspect {
 
    @Around("@annotation(operLog)")
    public Object record(ProceedingJoinPoint pjp, OperLog operLog) throws Throwable {
        String method = pjp.getSignature().toShortString();
        Object[] args = pjp.getArgs();
        Object result = pjp.proceed();
        // 持久化日志...
        return result;
    }
}

接口限流

@Around("@annotation(rateLimit)")
public Object rateLimit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
    String key = pjp.getSignature().toString();
    if (!rateLimiter.tryAcquire(key, rateLimit.qps())) {
        throw new RuntimeException("请求过于频繁");
    }
    return pjp.proceed();
}

参数校验

@Before("execution(* com.example.controller.*.*(..))")
public void validateArgs(JoinPoint jp) {
    for (Object arg : jp.getArgs()) {
        if (arg == null) throw new IllegalArgumentException("参数不能为空");
    }
}

代理机制

目标类实现了接口  →  JDK 动态代理(基于接口)
目标类未实现接口  →  CGLIB 代理(基于子类继承)

强制使用 CGLIB(proxyTargetClass = true):

@EnableAspectJAutoProxy(proxyTargetClass = true)

或在配置文件中:

spring:
  aop:
    proxy-target-class: true

注意事项

  • 自调用失效:同类内部方法互调不经过代理,AOP 不生效。解决方案:注入自身 Bean 或用 AopContext.currentProxy()
  • private 方法:Spring AOP 基于代理,无法拦截 private 方法
  • final 类/方法:CGLIB 无法代理 final 类或方法
  • 多切面排序:用 @Order(n) 控制多个切面的执行顺序,n 越小优先级越高