研讨会
HOME
研讨会
正文内容
AI夸夸助手:2026-04-10 Spring AOP 从入门到精通,手写代码搞定面试必考
发布时间 : 2026-04-27
作者 : 小编
访问数量 : 21
扫码分享至微信

如果你是 AI夸夸助手 的忠实读者,欢迎来到本期技术深度拆解。

面向切面编程(Aspect-Oriented Programming,AOP)是 Spring 框架两大核心特性之一,与 IoC 并称为 Spring 的基石-10。据统计,2025 年 Java 生态中已有

78% 的企业级应用使用 AOP 解决横切关注点问题,传统 OOP 在日志、事务等场景的代码重复率高达

60% 以上-10

很多开发者虽然能用 AOP,却在面试中被问“JDK 动态代理和 CGLIB 有什么区别”时卡住,或者在自己的项目中遇到切面不生效却不知从何排查。今天这篇文章将带你从零系统掌握 Spring AOP:先搞懂核心概念,再看底层原理,最后附上可直接运行的代码和面试高频题,让你真正“理解概念、理清逻辑、看懂示例、记住考点”。

一、痛点切入:为什么你的代码越来越“脏”?

在传统的面向对象编程(OOP)中,代码按照业务功能垂直组织。这种模式在处理“横切”需求时会暴露出明显的痛点。来看一段典型代码:

java
复制
下载
@Service
public class UserService {
    
    public void createUser(String username) {
        // 手动日志 - 重复代码
        System.out.println("【日志】开始执行 createUser,参数:" + username);
        
        // 手动性能监控 - 重复代码
        long start = System.currentTimeMillis();
        
        // 核心业务逻辑
        System.out.println("创建用户:" + username);
        
        // 手动性能监控结束 - 重复代码
        long end = System.currentTimeMillis();
        System.out.println("【性能】createUser 耗时:" + (end - start) + "ms");
        
        // 手动日志结束 - 重复代码
        System.out.println("【日志】createUser 执行完成");
    }
    
    public void deleteUser(Long userId) {
        // 同样的日志和性能代码又写了一遍...
        // 如果有 50 个方法,就要写 50 遍!
    }
}

这段代码的三大问题:

问题说明
代码冗余日志、性能监控、事务管理等代码在每个方法中重复出现,开发效率低
关注点混杂核心业务逻辑与系统级服务交织在一起,代码难以阅读和维护
扩展性差若要修改日志格式或增加新的切面功能(如安全校验),需修改所有相关方法

这正是 AOP 要解决的问题。AOP 将这些分散在多处、与核心业务无关的横切关注点(Cross-cutting Concerns)抽取出来,封装成独立的“切面”,在不修改原有业务代码的前提下动态织入增强逻辑-

二、核心概念:AOP 的七个关键术语

在正式编码之前,必须掌握 AOP 的七个核心概念-22

术语英文含义类比
连接点Join Point程序执行过程中可以插入切面的点(如方法调用、异常抛出)地铁站里的每一个出入口
切点Pointcut匹配连接点的表达式,决定在哪些连接点上应用通知指定从哪个出口出站
通知Advice在特定连接点上执行的动作(前置/后置/环绕等)在出口处做的事情(刷卡、安检)
切面Aspect切点 + 通知的封装模块整个安检流程
目标对象Target Object被增强的原始业务对象乘客本人
代理ProxyAOP 框架生成的对象,包裹目标对象以插入切面逻辑安检通道(乘客经过)
织入Weaving将切面代码与目标对象关联的过程把安检规则施加到乘客身上

一句话总结:在 连接点 中,用 切点 选定一批目标方法,在这些方法执行前后,通过 通知 插入增强逻辑;将切点和通知打包就是 切面;通过 织入 生成 代理对象,最终增强 目标对象

通知(Advice)的五种类型-11

注解执行时机典型用途
@Before目标方法执行前参数校验、权限检查
@After目标方法执行后(无论是否异常)资源清理、释放连接
@AfterReturning目标方法正常返回后记录返回值、缓存更新
@AfterThrowing目标方法抛出异常后异常告警、事务回滚
@Around环绕整个方法执行,可控性能监控、事务管理(最强大)

@Around 通知是使用频率最高的类型,因为它既能控制目标方法是否执行,又能同时获取方法执行前后的完整信息。注意必须调用 proceed() 才能执行目标方法,并且要有返回值。

三、代理模式与动态代理:AOP 的底层基石

Spring AOP 的实现本质上依赖于 代理模式(Proxy Pattern) ,这是理解 AOP 原理的关键-21

3.1 静态代理

先理解什么是代理:代理类作为中间层,在调用真实对象前后插入额外逻辑。用一个房屋中介的例子说明-21

java
复制
下载
// 1. 抽象主题:定义业务接口
public interface HouseSubject {
    void rentHouse();  // 房屋租赁
}

// 2. 真实主题:业主(真正的业务逻辑)
public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("业主执行:签订租约 → 交付房屋");
    }
}

// 3. 代理类:中介(在真实业务前后插入增强逻辑)
public class HouseProxy implements HouseSubject {
    private HouseSubject realSubject;
    
    public HouseProxy(HouseSubject realSubject) {
        this.realSubject = realSubject;
    }
    
    @Override
    public void rentHouse() {
        // 前置增强
        System.out.println("【中介】审核租客资质 → 准备租赁合同");
        // 调用真实业务
        realSubject.rentHouse();
        // 后置增强
        System.out.println("【中介】收取中介费 → 跟进租后服务");
    }
}

// 使用
public class Client {
    public static void main(String[] args) {
        HouseSubject proxy = new HouseProxy(new RealHouseSubject());
        proxy.rentHouse();  // 调用代理,自动获得增强功能
    }
}

静态代理的缺点也很明显:每个真实类都需要写一个对应的代理类,当项目中有上百个类需要增强时,代码量会急剧膨胀。

3.2 动态代理:Spring AOP 的灵魂

动态代理在运行时动态生成代理类,无需为每个类手动编写代理。Spring AOP 支持两种动态代理方式--11

对比维度JDK 动态代理CGLIB 动态代理
实现方式基于接口生成代理类基于继承生成目标类的子类
适用条件目标类必须实现至少一个接口目标类无接口(或强制使用)
无法代理的对象无接口的类final 类 / final 方法
性能反射调用,略低直接调用,略高
依赖JDK 原生,无需额外依赖需要 CGLIB 库(Spring 已整合)

Spring AOP 默认优先使用 JDK 动态代理;当目标类未实现任何接口时,自动切换为 CGLIB 代理-。可通过配置强制使用 CGLIB:@EnableAspectJAutoProxy(proxyTargetClass = true)

四、代码示例:从零搭建 Spring Boot AOP

4.1 添加依赖

pom.xml 中添加 AOP 起步依赖-29

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

4.2 创建切面类

下面是一个完整的日志切面,实现了统一日志记录 + 接口耗时统计-12

java
复制
下载
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.;
import org.springframework.stereotype.Component;

@Aspect          // 标记为切面类
@Component       // 交由 Spring 容器管理
public class LoggingAspect {
    
    /
      切点:匹配 com.example.service 包下所有类的所有方法
      execution 表达式格式:[修饰符] 返回类型 包名.类名.方法名(参数)
     /
    @Pointcut("execution( com.example.service..(..))")
    public void servicePointcut() {}
    
    // 前置通知:方法执行前执行
    @Before("servicePointcut()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("【前置】调用前:" + joinPoint.getSignature().getName());
    }
    
    // 后置通知:方法执行后执行(无论是否异常)
    @After("servicePointcut()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("【后置】方法完成:" + joinPoint.getSignature().getName());
    }
    
    // 环绕通知:最强大,可控制方法执行流程并统计耗时
    @Around("servicePointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        long startTime = System.currentTimeMillis();
        
        System.out.println("【Around 前】进入方法:" + methodName);
        
        // 执行目标方法
        Object result = joinPoint.proceed();
        
        long endTime = System.currentTimeMillis();
        System.out.println("【Around 后】方法 " + methodName + " 耗时:" 
                + (endTime - startTime) + "ms");
        
        return result;
    }
}

关键点解析

  • @Aspect + @Component:声明切面类并由 Spring 管理-47

  • @Pointcut:定义切点表达式,复用性强

  • ProceedingJoinPoint:环绕通知专用,调用 proceed() 执行目标方法

  • joinPoint.getSignature():获取被拦截的方法信息

4.3 自定义注解实现 AOP(更优雅的方式)

除了用表达式匹配,还可以通过自定义注解实现更精准的拦截-29

java
复制
下载
// 1. 创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
}

// 2. 在切面中使用注解作为切入点
@Pointcut("@annotation(com.example.demo.Loggable)")
public void loggablePointcut() {}

// 3. 在目标方法上添加注解
@Loggable
public User getUserById(Long id) {
    return userRepository.findById(id);
}

4.4 新旧方式对比

维度传统方式(OOP)AOP 方式
代码位置日志代码分散在各业务方法中日志逻辑集中在切面类
修改成本改一处需改所有方法只改切面类
业务代码纯净度日志代码与业务逻辑混在一起业务代码只关注核心逻辑
新增功能需要修改所有相关方法只需添加新的切面类

五、底层原理:代理对象是如何生成的?

当 Spring 容器初始化 Bean 时,AOP 的处理流程如下--20

  1. Bean 后置处理AnnotationAwareAspectJAutoProxyCreator 在 Bean 初始化后介入

  2. 代理判断:检查 Bean 是否匹配任意切点

  3. 代理创建:通过 DefaultAopProxyFactory 决定代理方式

    • 目标类实现了接口 → JDK 动态代理

    • 目标类无接口 → CGLIB 代理

  4. 通知链构建:将匹配的通知构建为 MethodInterceptor

  5. 代理返回:返回代理对象,注入到其他 Bean 中

底层依赖的知识点:动态代理的核心依赖于 Java 反射机制——JDK 代理通过 InvocationHandler.invoke() 实现,CGLIB 通过 ASM 字节码技术生成子类。这也是面试中经常被追问“反射的应用场景”的原因-

Spring AOP 属于 运行时织入(Runtime Weaving) ,即在程序运行时动态生成代理,而非在编译时修改字节码。这与 AspectJ 形成对比-22

对比项Spring AOPAspectJ
织入时机运行时动态代理编译时/类加载时织入
连接点范围仅支持方法级别支持字段、构造器、静态代码块等
性能略有损耗(运行时生成代理)更高(编译时已织入)
使用复杂度简单轻量,适合大多数场景复杂但功能强大

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

题目1:什么是 AOP?它的核心思想是什么?

标准答案:AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,其核心思想是将横切关注点(如日志、事务、安全等)从核心业务逻辑中分离出来,封装成可重用的切面模块,通过预编译方式或运行期动态代理在目标方法前后织入增强逻辑,从而提高代码的模块化程度和可维护性-

踩分点:① 说出全称 ② 点出“横切关注点” ③ 说明“分离”和“织入”

题目2:Spring AOP 的底层原理是什么?JDK 动态代理和 CGLIB 有什么区别?

标准答案:Spring AOP 底层基于动态代理实现,支持两种代理方式-1

  • JDK 动态代理:要求目标类实现接口,通过 java.lang.reflect.ProxyInvocationHandler 在运行时生成代理类,基于反射调用目标方法

  • CGLIB 代理:针对无接口的类,通过字节码技术生成目标类的子类,重写父类方法实现增强,但无法代理 final 类或 final 方法

Spring 默认优先使用 JDK 动态代理,当目标类未实现接口时自动切换为 CGLIB。

踩分点:① 说出两种代理方式 ② 说清各自原理 ③ 指出适用条件差异 ④ 说明 Spring 的默认选择策略

题目3:AOP 中的通知有哪些类型?各有什么特点?

标准答案:AOP 有五种通知类型-22

  • @Before:前置通知,在目标方法执行前触发,适用于参数校验、权限控制

  • @After:后置通知,方法执行后触发(无论是否异常),适用于资源清理

  • @AfterReturning:返回通知,方法正常返回后触发,可获取返回值

  • @AfterThrowing:异常通知,方法抛出异常后触发,适用于异常告警

  • @Around:环绕通知,最强大,可在方法执行前后都插入逻辑,需手动调用 proceed() 执行目标方法

踩分点:① 列出五种类型 ② 说清各自触发时机 ③ 说明 @Around 的特殊性(需手动调用 proceed()

题目4:Spring AOP 切面不生效可能有哪些原因?

标准答案:常见原因如下-46

  1. 缺少 AOP 依赖或未启用 @EnableAspectJAutoProxy

  2. 切面类未被 @Component 注解且未被 Spring 容器扫描到

  3. 切点表达式写错,导致未匹配到任何目标方法

  4. 目标方法为 privatefinal(CGLIB 无法代理)

  5. 同类内部方法调用 this.method(),绕过了代理对象

  6. 目标对象非 Spring 容器管理的 Bean

踩分点:① 依赖/配置问题 ② 切点表达式问题 ③ 同类调用问题 ④ final/private 限制

题目5:execution 切入点表达式怎么写?

标准答案execution 表达式格式为-22

text
复制
下载
execution([修饰符] 返回类型 包名.类名.方法名(参数))

常用示例:

  • execution( com.example.service..(..)):匹配该包下所有类的所有方法

  • execution(public void com.example...(..)):匹配 com.example 包及子包下所有 public void 方法

  • execution( com.example.UserService.get(..)):匹配 UserService 中以 get 开头的方法

七、结尾总结

本文系统讲解了 Spring AOP 的核心知识点:

核心知识回顾

  • 是什么:AOP 是一种解决横切关注点的编程范式,将日志、事务等公共功能从业务逻辑中剥离

  • 怎么用:通过 @Aspect + @Component 定义切面,配合五种通知类型在目标方法前后插入逻辑

  • 为什么能工作:底层依赖动态代理——JDK 动态代理(基于接口)和 CGLIB 代理(基于继承)

  • 面试考什么:概念理解、代理区别、通知类型、失效原因、表达式写法

重点与易错点

  • 切面不生效最常见的原因:同类内部调用 this.method() 绕过代理

  • 切点表达式写错导致切面不生效,务必用 @Pointcut 单独定义便于调试

  • @Around 通知必须调用 proceed() 并返回结果,否则目标方法不会执行

进阶预告:下一篇文章将深入讲解 AOP 在实际项目中的最佳实践——如何用 AOP 实现可插拔的日志追踪系统、分布式链路追踪以及性能监控,敬请关注!

📚 参考资料

  1. [1] AOP(面向切面编程)——开发者社区. 阿里云开发者社区,2025.

  2. [2] Spring AOP 深度解析与项目实战. 腾讯云开发者社区,2025.

  3. [3] 搞懂 AOP:核心概念 + 实战案例 + 事务失效终极排查指南. 华为云开发者社区,2025.

  4. [4] Spring AOP实现原理. 华为云开发者社区,2025.

  5. [5] 如何在Spring Boot项目中使用Spring AOP. 腾讯云开发者社区,2025.

  6. [6] 百度后端开发(Java)面试题精选. Java知识分享网,2026.

  7. [7] 谈谈 Spring 的 AOP. quanxiaoha.com,2026.

  8. [8] 如何在Spring中实现基于注解的AOP. 图灵资讯,2026.

  9. [9] Java Web学习日报——AOP面向切面编程实战. cnblogs.com,2026.

王经理: 180-0000-0000(微信同号)
10086@qq.com
北京海淀区西三旗街道国际大厦08A座
©2026  上海羊羽卓进出口贸易有限公司  版权所有.All Rights Reserved.  |  程序由Z-BlogPHP强力驱动
网站首页
电话咨询
微信号

QQ

在线咨询真诚为您提供专业解答服务

热线

188-0000-0000
专属服务热线

微信

二维码扫一扫微信交流
顶部