芯片中心

2026年4月科研绘图AI助手:Spring AOP面向切面编程从入门到面试通关

小编 2026-04-22 芯片中心 23 0

本文是【科研绘图AI助手】技术系列的首篇核心文章,带你用最短时间掌握Spring AOP的知识体系

在Spring框架的两大核心思想中,如果说IoC(Inversion of Control,控制反转)解决了对象管理的问题,那么AOP(Aspect-Oriented Programming,面向切面编程)则解决了代码解耦的痛点。据统计,2025年Java生态中约78%的企业级应用都在使用AOP解决横切关注点问题-2

很多开发者在实际工作中“会用AOP”,但往往停留在“复制粘贴”阶段:切点表达式写不对就百度,遇到@Transactional失效就挠头,面试时说不清JDK动态代理和CGLIB的区别。这种“只会用、不懂原理”的状态,正是进阶路上的绊脚石。

本文将从痛点出发,讲透AOP的概念体系、底层原理,并附上可直接运行的代码示例和高频面试题,帮助你建立完整的知识链路。


一、痛点切入:为什么需要AOP?

想象一下,你正在开发一个电商系统,有登录、下单、支付、查询等业务方法。每个方法都需要添加日志记录、权限校验、事务控制和性能监控。传统做法是在每个方法里重复编写这些逻辑:

java
复制
下载
// 传统写法:业务逻辑与横切关注点严重耦合
public void placeOrder(Order order) {
    // 日志记录
    logger.info("开始下单...");
    // 权限校验
    if (!hasPermission()) throw new SecurityException();
    // 事务开启
    transaction.begin();
    try {
        // 核心业务逻辑
        orderService.create(order);
        transaction.commit();
    } catch (Exception e) {
        transaction.rollback();
        logger.error("下单失败", e);
    }
    // 性能监控
    recordTime();
}

这种写法的缺点显而易见:

  • 代码重复率高:传统OOP在日志/事务等场景的代码重复率可高达60%以上-2

  • 耦合度高:业务代码与非功能性代码混杂

  • 维护困难:修改日志格式或权限策略时,需要改动所有业务方法

AOP的核心价值正是在于将这些横切关注点(Cross-Cutting Concerns)从核心业务逻辑中抽离出来,通过“切面”进行统一处理-2


二、核心概念讲解:切面(Aspect)

Aspect(切面) 是AOP中最核心的概念,它将横切关注点进行模块化封装。简单来说,切面就是“你想干什么”+“在哪儿干”的结合体。

用一个生活化的类比来理解:

  • 一家连锁餐厅里,每个门店(业务方法)都会涉及到食材采购、卫生检查、收银这些通用环节

  • 餐厅总部将这些通用环节统一制定成标准流程,这就是“切面”

  • 每个门店执行标准流程时,不需要重新设计,直接套用即可

在Spring AOP中,切面的标准定义是:切面 = 切点(Pointcut)+ 通知(Advice)-48

切面的核心价值在于:

  • 将通用功能(日志、事务、权限)从业务代码中剥离

  • 提高代码复用率和可维护性

  • 实现“关注点分离”的设计原则


三、关联概念讲解:通知(Advice)与切点(Pointcut)

通知(Advice)

Advice(通知) 定义了切面在特定连接点执行的“动作”,也就是“在什么时候、做什么事”。Spring AOP提供了五种通知类型-1

注解类型执行时机典型应用
@Before前置通知目标方法执行前参数校验、权限控制
@After后置通知目标方法执行后(无论是否异常)资源清理
@AfterReturning返回后通知目标方法正常返回后访问返回值进行加工
@AfterThrowing异常通知目标方法抛出异常后统一异常处理
@Around环绕通知包裹目标方法,可控制执行流程性能监控、事务管理

切点(Pointcut)

Pointcut(切点) 定义了哪些连接点会被切面处理,即“在哪儿干”。它通过表达式来匹配一组连接点-1

java
复制
下载
// 常用切点表达式示例
execution( com.example.service..(..))     // 匹配service包下所有类的所有方法
@annotation(com.example.anno.Log)            // 匹配被@Log注解标记的方法
within(com.example.service.UserService)      // 匹配UserService类中的所有方法

切面、切点、通知的关系

三者是AOP的核心三角:

  • 切点指定“哪些方法需要被增强”

  • 通知指定“在方法的哪个阶段做什么事”

  • 切面 = 切点 + 通知,把“哪里”和“做什么”组合成一个完整的增强模块-48

用一句话总结:切面 = 切点 + 通知


四、代码示例:用Spring AOP实现方法耗时统计

下面是一个完整的AOP示例,实现每个Controller接口的执行耗时统计-48

步骤1:引入依赖

xml
复制
下载
运行
<!-- Spring Boot AOP Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

步骤2:编写切面类

java
复制
下载
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect           // ① 标记该类为切面类
@Component        // ② 将切面类交给Spring容器管理
public class TimeAspect {

    // ③ @Around环绕通知 + 切点表达式:匹配controller包下所有类的所有方法
    @Around("execution( com.example.library.controller..(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();      // 前置增强:开始计时
        
        Object result = joinPoint.proceed();          // ④ 执行目标方法
        
        long end = System.currentTimeMillis();        // 后置增强:结束计时
        log.info("方法 {} 执行耗时: {} ms", joinPoint.getSignature().getName(), end - start);
        return result;
    }
}

执行流程说明

  1. 当调用Controller中的方法时,Spring AOP会先执行recordTime方法

  2. 执行前置增强(开始计时)→ 调用joinPoint.proceed()执行原始业务方法 → 执行后置增强(结束计时)

  3. 最终将业务方法的返回值返回给调用方

关键点

  • @Around环绕通知必须手动调用proceed()才能执行目标方法,否则目标方法不会被执行

  • ProceedingJoinPoint参数只能在@Around通知中使用

  • 切面类必须被Spring容器管理,因此需要添加@Component注解-13


五、底层原理:动态代理机制

Spring AOP之所以能在不修改原始代码的情况下增强方法,底层依赖于动态代理技术。其本质是代理模式在框架设计中的应用-31

Spring AOP根据目标类的特征,智能选择两种代理实现方式-1-4

JDK动态代理

  • 适用条件:目标对象实现了至少一个接口

  • 实现原理:基于Java反射机制,通过java.lang.reflect.ProxyInvocationHandler在运行时生成实现目标接口的代理类-21

  • 代理对象行为:代理类实现目标接口,方法调用被转发到InvocationHandler.invoke()方法,在该方法中插入增强逻辑

CGLIB动态代理

  • 适用条件:目标对象没有实现接口(或配置强制使用CGLIB)

  • 实现原理:通过字节码技术创建目标类的子类,在子类中重写父类方法并在方法调用前后插入切面逻辑-21

  • 注意事项final类和final方法无法被CGLIB代理(因为无法被继承或重写)-43

代理选择策略

Spring框架的默认策略是“有接口就用JDK,没接口就用CGLIB”;Spring Boot 2.0版本开始默认使用CGLIB代理-。开发者可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB代理-4

底层技术依赖

Spring AOP的底层实现依赖以下核心技术:

  • 反射机制(Reflection):JDK动态代理的核心支撑

  • 字节码操作(Bytecode Manipulation):CGLIB依赖的ASM字节码框架

  • 代理模式(Proxy Pattern):经典的GoF设计模式

  • 责任链模式(Chain of Responsibility):管理多通知的执行顺序-1


六、高频面试题与参考答案

1. 什么是AOP?【必考】

参考答案:AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许在不修改原有业务代码的前提下,通过动态代理在方法执行前后统一添加横切逻辑(如日志、事务、权限)。它是Spring框架两大核心思想之一,与IoC相辅相成-43

踩分点:编程范式 + 不改代码 + 横切逻辑 + 动态代理 + Spring核心

2. Spring AOP 和 AspectJ 有什么区别?

维度Spring AOPAspectJ
织入时机运行时动态代理编译时或类加载时
功能范围仅支持方法级连接点支持字段、构造器等更丰富的连接点
性能略低(运行时生成代理)更高(编译时优化)
使用场景轻量级应用,无需复杂切面企业级复杂切面需求

标准答案:Spring AOP是运行时动态代理实现,功能相对简单;AspectJ是编译时织入,功能更强大。Spring AOP借鉴了AspectJ的注解语法,但底层实现机制不同-1-43

3. JDK动态代理和CGLIB有什么区别?如何选择?

参考答案

  • JDK动态代理:基于接口实现,要求目标类必须有接口;性能较好,代码量小

  • CGLIB:基于继承实现,通过生成子类创建代理;不需要接口,但final类/方法无法代理

选择策略:Spring默认优先使用JDK动态代理,若目标类无接口则自动降级为CGLIB;Spring Boot 2.0后默认使用CGLIB--43

4. @Transactional 为什么有时会失效?

参考答案:失效的常见原因:

  1. 方法不是public的(事务只作用于public方法)

  2. 同一个类内部调用(没有经过代理对象,AOP不生效)

  3. final方法无法被代理

  4. 异常被捕获后未重新抛出-43

记忆口诀:公共方法 + 外部调用 + 非final + 异常抛出

5. @Around和@Before/@After有什么区别?

参考答案@Before@After只能分别在方法执行前/后执行增强逻辑,不能控制目标方法的执行;@Around是功能最强大的通知,可以通过ProceedingJoinPoint.proceed()完全控制目标方法的执行时机,甚至可以决定是否执行原方法-43


七、总结回顾

本文围绕Spring AOP的核心知识体系展开,要点总结如下:

知识模块核心要点
核心概念切面 = 切点 + 通知;五种通知类型(Before/After/AfterReturning/AfterThrowing/Around)
代理机制JDK动态代理(基于接口)vs CGLIB(基于继承),默认优先JDK
底层原理反射 + 字节码操作 + 代理模式,运行时动态织入
常见陷阱@Transactional失效场景、内部调用不走代理、final方法无法代理
面试重点概念理解 + 代理选择策略 + 失效原因分析

重点与易错点提醒

  • 切点表达式是AOP的“灵魂”,务必掌握execution的基本语法

  • @Around环绕通知必须手动调用proceed(),否则目标方法不会执行

  • 内部方法调用不会触发AOP增强,因为调用的是this对象而非代理对象

  • final类和final方法无法被CGLIB代理


下篇预告:我们将深入Spring AOP的源码层面,剖析ProxyFactory的代理创建流程和通知执行的责任链实现,敬请期待!

本文内容首发于【科研绘图AI助手】,欢迎关注,获取更多技术干货。

猜你喜欢