SpringAOP

一、什么是AOP

AOP 即 Aspect Oriented Programming 的缩写,即是面向切面编程。

什么叫做面向切面编程呢,切面就是指某一类特定问题,所以AOP也可以理解为面向特定方法编程。AOP可以做到在不改动原始方法的基础上,针对特定的方法进行功能的增强。可以在程序运行期间在不修改代码的基础上对已有方法进行增强。

总的来说,AOP是一种思想,是对某一类事情的集中处理

二、AOP解决了什么样的问题

AOP(面向切面编程)主要解决的是横切关注点带来的代码分散和纠缠问题。

简单说,在传统面向对象编程(OOP)中,有些逻辑(如日志,事务,权限)不属于某个具体的业务模块,却散落在很多模块里,导致代码难以维护。AOP就是把这些逻辑从业务代码中抽离出来,统一处理。

在OOP里,我们按功能划分模块。但有些功能会"横切"多个模块,比如:

  • 日志记录:每个重要方法可能都要加日志
  • 事务管理:多个业务方法需要开启/提交/回滚事务
  • 权限校验:很多接口调用前都要检查权限
  • 性能监控:需要统计某些方法的执行事件
  • 异常处理:统一处理特定异常

这些逻辑如果直接写在业务方法里,会导致:

  1. 代码重复:同样的日志/事务代码到处复制
  2. 业务逻辑被污染:业务方法里混杂大量非业务代码,可读性差
  3. 维护困难:改一个日志格式,要改几十个地方
  4. 职责不单一:一个方法既处理业务,有管理日志和事务

AOP的解决方式

AOP通过切面把这些横切逻辑模块化,然后通过织入(Weaving)在编译期、类加载期或运行期,动态地把切面代码"插入"到目标方法地合适位置(如方法执行前、后、异常时)。

这样,业务代码只需要关心业务,日志、事务等由切面统一处理。比如:

java 复制代码
// 业务代码只写业务
public void createOrder(Order order) {
    orderRepository.save(order);
}
java 复制代码
// 切面统一处理日志和事务
@Aspect
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint jp) {
        System.out.println("调用方法: " + jp.getSignature());
    }
}

总的来说:

AOP的主要价值是:

  • 减少重复代码:横切逻辑只写一次
  • 提高模块化:业务与非业务逻辑分离
  • 易于维护:修改横切逻辑只需修改切面
  • 增强可读性:业务方法更纯净

典型应用场景:

  • 日志记录
  • 事务管理(如Spring的@Transactional)
  • 权限/安全校验
  • 缓存
  • 性能监控
  • 统一异常处理
  • 数据校验

所以,AOP解决的核心问题是:将横切关注点从业务逻辑中解耦出来,集中管理,从而减低耦合、减少重复、提高可维护性。

三、如何使用 Spring AOP

AOP 是面向切面编程的一种思想,而 Spring AOP 就是AOP思想的一种实现。

那么如何使用Spring AOP 呢

概括地说:

Spring AOP 的用法 = 定义切面(切点 + 通知),交给 Spring 管理,运行时自动织入。

核心概念对照:

概念 注解/关键字 作用
切面 @Aspect 横切逻辑的模块
切点 @Pointcut 决定在哪些方法上织入
通知 @Before / @After / @Around 决定织入后做什么
连接点 JoinPoint 被拦截的方法,可获取方法名、参数
织入 Weaving 把切面应用到目标方法的过程

具体地说:

1、加依赖

java 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Spring Boot 会开启AOP代理,无需手动加 @EnableAspectJAutoProxy

2、写业务代码

如:

java 复制代码
@Service
public class OrderService {
    public void createOrder(Long id) {
        System.out.println("创建订单:" + id);
    }
}

3、定义切面

java 复制代码
@Aspect        // 声明这是切面
@Component     // 交给 Spring 管理
public class LogAspect {

    // 切点:匹配 com.example.service 包下所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 前置通知:方法执行前
    @Before("serviceMethods()")
    public void before(JoinPoint jp) {
        System.out.println("[前置] 调用:" + jp.getSignature().getName());
    }

    // 后置返回:方法正常返回后
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("[返回] 结果:" + result);
    }

    // 最终通知:无论成功失败都执行
    @After("serviceMethods()")
    public void after() {
        System.out.println("[最终] 方法结束");
    }

    // 环绕通知:功能最强,可控制执行、改参数、改返回值
    @Around("serviceMethods()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();          // 执行目标方法
        } finally {
            long cost = System.currentTimeMillis() - start;
            System.out.println("[环绕] 耗时:" + cost + "ms");
        }
    }
}

第4步:调用业务方法,切面自动生效

java 复制代码
orderService.createOrder(1L);

输出为:

前置 调用:createOrder

创建订单:1

环绕 耗时:1ms

返回 结果:null

最终 方法结束

五种通知的对比

通知 注解 时机 能否控制执行
前置 @Before 方法前
后置返回 @AfterReturning 正常返回后 可读返回值
后置异常 @AfterThrowing 抛异常后 可读异常
最终 @After 方法结束后(总执行)
环绕 @Around 包裹整个方法

三个必须要知道的坑

原因 解决
同类内部方法调用失效 Spring AOP 基于代理,外部调用先经过代理对象,代理才能织入通知;而类内部 this.methodB() 走的是原始对象,不经过代理,所以切面失效 ① 注入自身 @Autowired private XxxService self;此时注入的是代理对象。 然后 self.methodB();② ((XxxService) AopContext.currentProxy()).methodB()(需开启 exposeProxy=true);③ 把方法拆到另一个 Bean
private / static / final 方法不能被切 private 方法在子类/代理中不可见,无法被覆盖;static 方法属于类而非对象,代理拦不到;final 方法不能被子类重写(CGLIB 靠生成子类)。因此动态代理机制无法在这些方法上织入 改为 public、非 static、非 final 方法;若必须增强,考虑用 AspectJ 的编译期/类加载期织入
代理方式由 proxyTargetClass 变量决定,不同环境默认值不同 Spring Framework 默认 proxyTargetClass = false:目标类有接口走 JDK 动态代理,无接口走 CGLIB;Spring Boot 2.x 的 AopAutoConfiguration 把默认值改为 true:强制走 CGLIB(即使实现了接口也生成子类代理) 通过 spring.aop.proxy-target-class=false 显式改回 JDK 优先;注意 @Transactional 的代理方式可能需单独用 @EnableTransactionManagement(proxyTargetClass = false) 配置

四、Spring AOP 的实现方式有几种

底层代理技术 有 2 种:JDK 动态代理和 CGLIB。切面定义方式有 4 种:@AspectJ 注解、XML aspect、XML advisor、Advisor API。实际开发中主要用 @AspectJ 注解 + Spring Boot 默认的 CGLIB 代理。"

方式 特点 适用场景
@AspectJ 注解 @Aspect@Before@Pointcut 等注解 首选,Spring 团队推荐
XML <aop:aspect> 在 XML 中配置切面 老项目、需要集中配置
XML <aop:advisor> aspectAdvisor 的"混血儿" 升级旧项目复用 Advice
Advisor API 直接实现 PointcutAdvisor 等接口 需要编程式、低版本 Spring

五、Spring AOP 原理

Spring AOP 通过动态代理为目标对象生成代理对象,在代理对象的方法前后织入通知,从而实现方法增强。

1、核心思想:代理+拦截

Spring AOP的本质是代理模式。

  • 调用方拿到的不是目标对象本身 ,而是代理对象

  • 代理对象在调用目标方法前后,插入通知逻辑

  • 所以业务代码零侵入,增强自动生效

这就是为什么"内部调用会失效" ------内部调用走的是 this(目标对象),绕过了代理对象。

2、两种代理技术

Spring AOP 底层只有两种代理

1、JDK动态代理

  • 条件:目标类至少实现一个接口

  • 原理:基于接口,在运行时生成一个实现了相同接口的代理类

  • 核心 APIjava.lang.reflect.Proxy + InvocationHandler

java 复制代码
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    (p, method, args) -> {
        System.out.println("[前置]");
        Object result = method.invoke(target, args);
        System.out.println("[后置]");
        return result;
    }
);

2、CGLB动态代理

  • 条件:是否实现接口都可代理

  • 原理:运行时生成目标类的子类,重写方法,在重写的方法里织入通知

  • 核心 APInet.sf.cglib.proxy.Enhancer + MethodInterceptor

java 复制代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);        // 继承目标类
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("[前置]");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("[后置]");
    return result;
});
UserService proxy = (UserService) enhancer.create();

最终用哪种代理,是"配置变量 + 目标类型"两个因素共同决定的。

roxyTargetClass 目标类有接口吗 实际用的代理
true 有接口 CGLIB(强制,不看接口)
true 无接口 CGLIB
false 有接口 JDK
false 无接口 CGLIB(自动退回)
相关推荐
爱码猿24 天前
SpringAop通过el表达式动态获取方法入参的值
spring boot·spring·aop
Lost of 程序猿1 个月前
AOP 实战:面向切面编程从理论到落地
后端·asp.net·aop·面向切片变成
白仑色1 个月前
Spring Boot 从切面统一控制事务
java·spring boot·aop·spring事务
折哥的程序人生 · 物流技术专研2 个月前
Java 23 种设计模式:从踩坑到精通 | 番外:代理 vs 装饰器 —— Spring AOP 到底用了哪个?
java·代理模式·装饰器模式·java面试·java设计模式·springaop·从踩坑到精通
折哥的程序人生 · 物流技术专研2 个月前
Java面试通关⑧:Spring核心IoC/AOP全集
ioc·校招·aop·大厂面试·java面试·spring核心·事务源码
屋外雨大,惊蛰出没3 个月前
深入浅出Spring Boot
java·spring boot·ioc·aop
JaydenAI3 个月前
[MAF预定义Agent中间件-03]FunctionInvocationDelegatingAgent:将AOP引入函数调用
ai·c#·agent·aop·maf
行者-全栈开发4 个月前
SpringBoot AOP 面向切面编程实战|4大企业级案例+自定义注解+5个避坑指南
spring boot·aop·权限校验·自定义注解·日志记录·避坑指南·切面编程
zz0723204 个月前
Redis + Lua 实现高性能分布式限流
redis·lua·aop·限流算法·分布式限流