AI助手截图带你搞懂Spring AOP:面向切面编程从入门到面试

小编头像

小编

管理员

发布于:2026年04月26日

3 阅读 · 0 评论

北京时间:2026年4月9日

不少开发者在学习Spring AOP时,会遇到一个常见痛点:会用注解做日志记录,但被问到“什么是AOP的核心概念”“JDK动态代理和CGLIB有什么区别”时,却说不出所以然。本文结合AI助手截图的资料,从痛点切入、概念拆解、代码示例到底层原理,带读者理清AOP的概念逻辑、看懂代理机制、记住高频面试考点,建立从“会用”到“懂原理”的完整知识链路。

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

传统实现方式存在的问题

先看一段“经典反面教材”代码。假设要在业务方法中添加日志记录:

java
复制
下载
// 一个简单的用户服务类
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(User user) {
        // 重复的日志代码
        System.out.println("【日志】开始保存用户:" + user.getName());
        long startTime = System.currentTimeMillis();
        
        // 核心业务逻辑
        System.out.println("正在保存用户到数据库...");
        
        // 重复的性能统计代码
        long costTime = System.currentTimeMillis() - startTime;
        System.out.println("【性能】方法耗时:" + costTime + "ms");
        System.out.println("【日志】用户保存成功");
    }
    
    @Override
    public void deleteUser(Long id) {
        System.out.println("【日志】开始删除用户:" + id);
        long startTime = System.currentTimeMillis();
        
        // 核心业务逻辑
        System.out.println("正在删除用户...");
        
        long costTime = System.currentTimeMillis() - startTime;
        System.out.println("【性能】方法耗时:" + costTime + "ms");
        System.out.println("【日志】用户删除成功");
    }
}

这段代码暴露了三大痛点:

痛点具体表现
代码重复日志、性能统计等代码在每个业务方法中反复出现
耦合度高业务逻辑与横切功能纠缠在一起,修改日志格式需要改动所有方法
维护困难横切逻辑散落在各处,难以集中管理和调整

这正是AOP(Aspect Oriented Programming,面向切面编程)要解决的问题。AOP的核心思想是“对某一类特定问题的集中处理”,将分散在各个业务方法中的横切逻辑(如日志、监控、权限)抽取出来,形成独立的“切面”,在程序运行时动态植入到目标方法中,实现无侵入式增强-1

二、核心概念讲解:AOP的五大术语

理解AOP,首先要掌握以下五个核心术语。可以用一个贴近生活的“公司打卡”类比来帮助理解-58

假设要给公司所有员工的“上班打卡”方法添加两个功能:①打卡前验证身份(前置增强);②打卡后记录日志(后置增强)。

  • 员工的 “上班打卡”方法 = 连接点

  • 所有需要打卡的 员工 = 切入点

  • 身份验证 + 日志记录 = 通知

  • 把通知绑定到切入点的 规则 = 切面

  • 整个添加增强的 过程 = 织入

术语详解

1. 切面(Aspect)

封装横切关注点的模块,包含多个通知和切点,如日志切面、事务切面、权限校验切面-6。在代码中用 @Aspect 注解标识。

2. 连接点(Join Point)

程序执行过程中的某个点(如方法调用、异常抛出),可以插入切面逻辑的位置。在Spring AOP中,主要指方法调用-6

3. 切入点(Pointcut)

通过表达式匹配一组连接点,定义哪些连接点会被切面处理-6。常用的切点表达式如下-6

表达式说明
execution( com.example.service..(..))匹配指定包下所有类的所有方法
@annotation(com.example.anno.Log)匹配被指定注解标记的方法
within(com.example.service.UserService)匹配指定类中的所有方法
args(java.lang.String)匹配参数类型为String的方法

4. 通知(Advice)

在特定连接点执行的动作,定义了“做什么”。Spring AOP支持五种通知类型-5-6

通知类型触发时机核心特点
@Before目标方法执行前无法阻止方法执行(除非抛异常)
@AfterReturning目标方法正常返回后可获取方法返回值
@AfterThrowing目标方法抛出异常后可捕获异常信息
@After目标方法执行后(无论是否异常)类似finally,总会执行
@Around目标方法执行前后(环绕)可控制目标方法的执行时机和是否执行,功能最强

5. 织入(Weaving)

将切面应用到目标对象并创建代理对象的过程。Spring AOP默认采用运行时织入-6

三、关联概念讲解:AOP与OOP

AOP与OOP的关系

AOP(Aspect Oriented Programming,面向切面编程)和OOP(Object Oriented Programming,面向对象编程)不是对立关系,而是互补关系。

维度OOPAOP
模块化单元类(Class)切面(Aspect)
关注点纵向继承关系横向横切逻辑
解决问题实体属性和行为的封装跨多个类的通用功能模块化

在OOP中,模块化的关键单元是类;而在AOP中,模块化的单元是切面。切面使得能够对跨多个类型和对象的关注点(如事务管理)进行模块化-。简单来说:OOP管“是什么”,AOP管“额外做什么”。

理解“横切关注点”

所谓“横切关注点”(Cross-cutting Concerns),是指那些散落在多个业务模块中、与核心业务逻辑无关但又必需的通用功能,如日志记录、安全控制、事务管理、性能监控等-40

四、概念关系总结

关系类型说明
AOP与OOP互补关系,OOP管理纵向继承,AOP管理横向切入
AOP思想与Spring AOP实现AOP是编程范式/思想,Spring AOP是其在Spring框架中的具体实现
切面与通知切面是“整体模块”,通知是切面中“具体的增强动作”
切入点与连接点切入点定义“哪些”连接点被增强(选择规则),连接点是“可被增强的位置”

一句话记忆口诀:切面定义规则,通知执行动作,切点筛选目标,织入完成整合,代理保障透明。

五、代码示例:3步实现AOP

下面通过一个“统计方法执行耗时”的实战案例,快速上手Spring AOP-1-27

第1步:引入依赖

在Spring Boot项目的pom.xml中添加:

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

第2步:编写切面类

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

@Aspect          // ① @Aspect:标记当前类为切面类
@Component       // ② @Component:将切面类交给Spring容器管理
@Slf4j
public class TimeAspect {
    
    // ③ @Pointcut:定义切入点(匹配service包下所有类的所有方法)
    @Pointcut("execution( com.example.demo.service..(..))")
    public void servicePointcut() {}
    
    // ④ @Before:前置通知,在目标方法执行前触发
    @Before("servicePointcut()")
    public void beforeMethod(JoinPoint joinPoint) {
        log.info("【前置通知】调用方法:{}.{},参数:{}",
            joinPoint.getTarget().getClass().getName(),
            joinPoint.getSignature().getName(),
            joinPoint.getArgs());
    }
    
    // ⑤ @Around:环绕通知,功能最强,可控制目标方法的执行
    @Around("servicePointcut()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        // 关键:调用proceed()执行目标方法
        Object result = joinPoint.proceed();
        
        long costTime = System.currentTimeMillis() - startTime;
        log.info("【环绕通知】方法 {} 执行耗时:{}ms", 
            joinPoint.getSignature().getName(), costTime);
        return result;
    }
    
    // ⑥ @AfterReturning:返回通知,目标方法正常返回后触发
    @AfterReturning(value = "servicePointcut()", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        log.info("【返回通知】方法 {} 返回结果:{}",
            joinPoint.getSignature().getName(), result);
    }
    
    // ⑦ @AfterThrowing:异常通知,目标方法抛出异常后触发
    @AfterThrowing(value = "servicePointcut()", throwing = "e")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception e) {
        log.error("【异常通知】方法 {} 抛出异常:{}",
            joinPoint.getSignature().getName(), e.getMessage());
    }
}

第3步:编写目标业务类并测试

java
复制
下载
@Service
public class UserService {
    public String getUserName(Long id) {
        // 模拟业务逻辑
        System.out.println("正在查询用户信息...");
        return "张三";
    }
}

关键理解:当调用userService.getUserName(1L)时,实际执行的是代理对象。执行流程为:前置通知 → 环绕通知前半段 → 目标方法 → 环绕通知后半段 → 返回通知,全程业务代码零侵入。

六、底层原理:Spring AOP如何实现

代理机制核心

Spring AOP的底层实现依赖于动态代理技术,其核心机制是通过代理对象拦截目标方法的调用,并在调用前后插入切面逻辑-5。Spring在底层使用两种动态代理技术-14

JDK动态代理 vs CGLIB代理

对比维度JDK动态代理CGLIB代理
代理方式接口代理子类代理
是否需要接口目标类必须实现接口不需要接口
实现原理基于java.lang.reflect.ProxyInvocationHandler,通过反射生成代理类基于ASM字节码生成技术,通过继承目标类生成子类
性能特点反射调用,方法执行时略慢生成代理类耗时较多,但方法调用性能更高
依赖仅需JDK标准库需要额外CGLIB依赖(Spring已内置)
final类/方法不适用无法代理final类,也无法重写final方法

选择策略:Spring AOP的底层代理方式取决于目标类是否实现接口。有接口时默认使用JDK动态代理,无接口时强制使用CGLIB-12

版本差异提醒:Spring Framework默认使用JDK动态代理,但从Spring 3.2开始内置了CGLIB,如果目标类没有实现接口,会自动切换到CGLIB。而Spring Boot 2.x将默认值改成了CGLIB-。若需强制指定,可通过配置@EnableAspectJAutoProxy(proxyTargetClass = true)实现-12

织入流程

织入发生在Spring容器启动阶段。Spring会扫描所有切面定义,根据切入点表达式匹配目标方法,在Bean初始化后通过postProcessAfterInitialization生成代理对象,将通知逻辑织入其中-11-5。这解释了为什么AOP对同类内部方法调用会失效——内部调用不经过代理对象。

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

面试题1:什么是AOP?Spring AOP的底层实现原理是什么?

踩分点:AOP定义 + 横切关注点概念 + 动态代理技术 + 两种代理方式说明

AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,它将横切关注点(如日志、事务、安全等非核心业务逻辑)从业务代码中分离出来,以切面形式集中管理。Spring AOP的底层依赖于动态代理技术,通过JDK动态代理(基于接口)或CGLIB代理(基于子类继承)在运行时生成代理对象,在目标方法调用前后织入增强逻辑-39

面试题2:JDK动态代理和CGLIB有什么区别?Spring如何选择?

踩分点:代理方式差异 + 接口要求 + 性能特点 + 选择策略

区别包括:①JDK基于接口,CGLIB基于子类继承;②JDK要求目标类实现接口,CGLIB无此限制;③JDK使用反射机制,CGLIB使用字节码生成;④性能上CGLIB方法调用更快,但生成代理类更慢;⑤JDK无法代理非接口方法,CGLIB无法代理final类和方法。Spring根据目标类是否实现接口自动选择:有接口默认用JDK,无接口自动切换到CGLIB-

面试题3:Spring AOP的五种通知类型分别是什么?适用场景是什么?

踩分点:五种通知名称 + 触发时机 + 典型场景

通知类型触发时机典型场景
@Before方法执行前参数校验、权限检查
@After方法执行后(必然执行)资源清理
@AfterReturning方法正常返回后记录返回值、结果转换
@AfterThrowing方法抛出异常后统一异常处理、错误日志
@Around方法执行前后(可控制执行)性能监控、事务控制、缓存

面试题4:为什么@Before里修改参数,目标方法收不到修改后的值?

踩分点:JoinPoint机制 + 参数引用特性 + @Around解决方案

Spring AOP的通知方法接收到的JoinPointProceedingJoinPoint中的参数是原始引用副本,@Before无法拦截并替换实际传入目标方法的参数。只有@Around能通过proceed(Object[] args)显式传入新参数数组实现参数修改。如果参数是可变对象(如Map、自定义DTO),在@Before里修改其字段是生效的,但这属于对象内部状态变更,不是“替换参数”-12

面试题5:@Aspect注解的切面类为什么必须由Spring容器管理?

踩分点:BeanPostProcessor机制 + Spring容器扫描过程

AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor,它只在Spring容器创建Bean的过程中扫描并处理标注了@Aspect已注册Bean。如果直接new出来的切面类,Spring根本看不到它,也不会为其生成代理,更不会触发任何通知逻辑。切面类必须通过@Component@Service或显式@Bean注册到Spring容器-12

八、结尾总结

本文围绕Spring AOP这一Spring框架的核心特性,从痛点出发,逐步讲解了:

学习模块核心要点
痛点分析传统开发中日志、事务等横切逻辑散落在各处,导致代码重复、耦合度高、维护困难
核心概念切面(Aspect)、连接点(Join Point)、切入点(Pointcut)、通知(Advice)、织入(Weaving)五大术语
与OOP关系AOP与OOP互补,共同构建清晰的程序结构
代码实战通过@Aspect、@Pointcut和各类通知注解,3步实现AOP
底层原理基于动态代理(JDK动态代理/CGLIB),在运行时织入增强逻辑
面试要点5道高频面试题及答案要点,涵盖定义、实现原理、通知类型等

重点易错点提醒

  • 切面类必须由Spring容器管理,不能直接new

  • 只有@Around能修改方法参数

  • AOP不拦截同类内部方法调用

  • Spring AOP仅支持方法级连接点

希望本文能帮助读者真正掌握Spring AOP,无论是日常开发还是面试备考,都能游刃有余。如需了解更多Spring相关技术,欢迎持续关注本系列后续内容!

标签:

相关阅读