首页 研发技术文章正文

实用Ai助手带你2026年彻底搞懂Spring AOP:从原理到面试一网打尽

研发技术 2026年05月05日 14:27 5 小编

2026年4月9日 | 技术科普 + 原理讲解 + 代码示例 + 面试要点

Spring AOP(Aspect Oriented Programming,面向切面编程) 是Spring框架两大核心支柱之一(另一为IoC)。据统计,2025年Java生态中已有78%的企业级应用使用AOP解决横切关注点问题-31。然而不少开发者在实际工作中却陷入“会用但讲不清”的窘境——知道怎么加@Before注解拦截日志,却说不明白JDK动态代理和CGLIB有什么区别;项目里用着AOP,面试时却被“Spring AOP为什么不支持方法内部调用”问住。本文将从痛点切入,由浅入深拆解AOP的核心概念、关联对比、代码实战、底层原理及高频面试题,帮你建立完整的知识链路。

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

先看一段“反面教材”。假设要在用户服务的每个业务方法前后加上日志和性能统计:

java
复制
下载
public class UserService {
    public User getUserById(Long id) {
        long start = System.currentTimeMillis();
        System.out.println("【日志】调用getUserById,参数:" + id);
        User user = userDao.findById(id);
        System.out.println("【日志】getUserById返回值:" + user);
        System.out.println("【性能】getUserById耗时:" + (System.currentTimeMillis() - start) + "ms");
        return user;
    }
    
    public void updateUser(User user) {
        long start = System.currentTimeMillis();
        System.out.println("【日志】调用updateUser,参数:" + user);
        // 业务逻辑...
        System.out.println("【日志】updateUser执行完毕");
        System.out.println("【性能】updateUser耗时:" + (System.currentTimeMillis() - start) + "ms");
    }
    // 其他方法重复同样的代码...
}

这种实现方式存在三大问题:代码冗余严重——同样的日志和统计逻辑在每个方法中反复出现;耦合度高——业务代码与横切逻辑混在一起,改一处日志格式要动所有方法;维护困难——新增横切功能(如权限校验)需要全局修改,极易遗漏。

这正是AOP要解决的问题——将日志、事务、安全等“横切关注点”从业务逻辑中抽离,统一管理、统一注入。

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

什么是切面(Aspect)?

切面:模块化的横切关注点,即将散落在各处的通用逻辑(如日志、事务)封装为一个独立的类,由切点(Pointcut)和通知(Advice)组成-1

用一个生活化的类比来理解:餐厅的后厨。厨师们专心做菜是“核心业务”,而切面就像是“餐厅经理”和“卫生监督员”——他们在每位厨师开始做菜前(前置通知)、完成后(后置通知)介入,检查卫生、记录出菜时间,这些横跨所有厨师工作的职责被统一管理,厨师无需关心。

在Spring AOP中,切面通常用@Aspect注解标记的Java类来实现。

切面解决了什么问题?

  • 解耦:业务代码不再混杂日志、事务等逻辑-1

  • 可维护:横切逻辑集中在切面中,改一处全应用生效

  • 非侵入:不修改原有业务类,符合开闭原则

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

通知(Advice)

通知:切面在特定连接点执行的具体动作,即“切面要做什么”-2

Spring AOP提供了5种通知类型:

注解类型执行时机能否控制执行/修改返回值
@Before前置通知目标方法执行前否(可修改对象参数内容,无法替换参数)
@AfterReturning返回通知目标方法正常返回后否(可访问返回值)
@AfterThrowing异常通知目标方法抛出异常后
@After后置通知目标方法执行后(无论是否异常)
@Around环绕通知包裹整个目标方法是(最强大,可控制执行、修改参数和返回值)

--13

切点(Pointcut)

切点:匹配连接点的表达式,用于决定通知应用到哪些方法上,即“切面要作用在哪儿”-1

Spring AOP使用AspectJ风格的切入点表达式,最常用的是execution关键字:

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

常用示例:

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

  • execution(public (..)):匹配所有公共方法

  • execution( com.example.service.UserService+.(..)):匹配UserService及其子类的所有方法-59

  • execution( com.example.service..get(..)):匹配所有以“get”开头的方法-59

💡 除了execution表达式,还可以使用自定义注解作为切入点,实现更精确的控制-21

四、概念关系与区别总结

AOP核心概念之间的关系可以用一句话概括:切面(Aspect)= 切点(Pointcut)+ 通知(Advice)

  • 切面是容器,定义了“在哪里做 + 做什么”

  • 切点负责筛选“在哪里做”,是一种筛选规则

  • 通知负责定义“做什么”,是具体的执行逻辑

  • 连接点是程序执行中的候选点,切点从这些候选点中筛选出目标

用一个表格强化对比:

概念英文一句话解释生活中的类比
连接点Join Point所有可能被拦截的位置每个厨师开始做菜的时刻
切点Pointcut筛选规则,决定拦截哪些连接点“只拦截川菜厨师”的规则
通知Advice拦截后执行的代码厨师开始前记录的时机
切面Aspect切点 + 通知的封装包含了“筛选规则”和“执行动作”的完整工作手册

五、代码示例:实战AOP日志切面

下面通过一个完整的日志记录切面示例,展示Spring AOP的实际应用。

步骤1:添加依赖(Spring Boot)

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

-21

步骤2:创建切面类

java
复制
下载
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
    // 定义切点:匹配service包下所有类的所有方法
    @Pointcut("execution( com.example.service..(..))")
    public void serviceLayer() {}
    
    // 前置通知:记录方法调用前的信息
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("调用方法:{},参数:{}",
            joinPoint.getSignature().getName(),
            Arrays.toString(joinPoint.getArgs()));
    }
    
    // 返回通知:记录方法返回结果
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("方法:{},返回结果:{}",
            joinPoint.getSignature().getName(), result);
    }
}

-13

步骤3:更强大的环绕通知(性能监控示例)

java
复制
下载
@Around("@annotation(com.example.annotation.PerfMonitor)")
public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.nanoTime();
    Object result = pjp.proceed();  // 执行目标方法
    long elapsed = System.nanoTime() - start;
    System.out.println(pjp.getSignature() + " 耗时:" + elapsed + "ns");
    return result;
}

关键说明

  • ProceedingJoinPoint专用于@Around通知,比普通JoinPoint多了一个proceed()方法,用于手动执行目标方法-69

  • @Around是最强大的通知类型,可控制是否执行目标方法、修改入参和返回值-13

执行结果示例

text
复制
下载
调用方法:getUserById,参数:[1]
方法:getUserById,返回结果:User{id=1, name='张三'}
com.example.service.UserService.getUserById 耗时:2385000ns

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

Spring AOP的底层依赖于动态代理机制。当容器启动时,Spring会为匹配切点的目标Bean创建代理对象,外部调用实际执行的是代理对象的方法,代理在方法执行前后织入通知逻辑-32

JDK动态代理 vs CGLIB

Spring AOP支持两种代理方式-

特性JDK动态代理CGLIB代理
原理基于接口,生成接口的代理实例基于继承,生成目标类的子类
要求目标类必须实现至少一个接口类不能是final,方法不能是private/final
性能反射调用,性能略低字节码操作,性能更优(约提升30%)
适用场景有接口的Bean无接口的Bean

选择规则:Spring默认优先使用JDK动态代理;若目标类未实现任何接口,自动切换到CGLIB-36。Spring 6.0之后默认启用CGLIB代理(proxyTargetClass=true),性能提升30%以上-52

技术支撑点:动态代理底层依赖Java反射机制和字节码生成技术(ASM/CGLIB库),这部分在后续进阶文章中会深入源码层面讲解。

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

问题1:Spring AOP和AspectJ有什么区别?

参考答案(核心踩分点):

  • 实现机制不同:Spring AOP基于运行时动态代理(JDK Proxy/CGLIB),在运行期生成代理对象;AspectJ基于编译时/类加载时字节码织入,直接修改字节码-

  • 功能范围不同:Spring AOP仅支持方法级别的拦截;AspectJ支持字段访问、构造器执行等更细粒度的连接点-

  • 性能差异:AspectJ性能更优(调用速度快2-8倍),Spring AOP略慢但配置更简单-

  • 定位不同:Spring AOP是轻量级解决方案,解决80%常见问题;AspectJ是完整AOP框架,适用于更复杂场景。

问题2:Spring AOP为什么不支持方法内部调用?

参考答案:Spring AOP基于代理实现。当类内部方法调用另一个方法时(如this.methodB()),调用的是原始对象而非代理对象,代理机制无法拦截。解决方案有:

  • 将方法提取到独立组件

  • 通过AopContext.currentProxy()获取当前代理对象再调用

  • 使用AspectJ编译时织入-43

问题3:Spring AOP中通知的执行顺序是怎样的?

参考答案:正常返回时的执行顺序为:

text
复制
下载
@Around(前置部分) → @Before → 目标方法 → @AfterReturning → @After → @Around(后置部分)

抛出异常时的顺序为:

text
复制
下载
@Around(前置部分) → @Before → 目标方法 → @AfterThrowing → @After → @Around(后置部分)

-36

问题4:@Around和@Before/@After的区别是什么?

参考答案@Around通知可以完全控制目标方法的执行,包括修改参数、跳过执行、修改返回值,且必须显式调用ProceedingJoinPoint.proceed()来执行目标方法。而@Before@After仅能在方法执行前后执行逻辑,无法控制执行本身-13

问题5:多个切面执行顺序如何控制?

参考答案:通过@Order注解指定执行顺序,数字越小优先级越高-13

java
复制
下载
@Aspect @Order(1)  // 先执行
public class TransactionAspect { ... }
@Aspect @Order(2)
public class LoggingAspect { ... }

八、结尾总结

回顾全文核心知识点:

AOP的定位:OOP的补充,专注解决横切关注点(日志、事务、安全)的模块化复用
核心概念:切面(Aspect)= 切点(Pointcut)+ 通知(Advice),连接点(Join Point)是被拦截的候选位置
5种通知类型:@Before、@After、@AfterReturning、@AfterThrowing、@Around(功能最强)
底层原理:基于JDK动态代理(有接口)和CGLIB(无接口)两种运行时代理机制
高频考点:AOP与AspectJ的区别、方法内部调用失效、通知执行顺序

易错提醒:不要忽略Spring AOP仅支持方法级拦截的限制;记住@Around必须调用proceed();内部自调用不会触发切面,这是面试中的高频坑点。

下一步学习方向:建议深入学习动态代理源码(Proxy.newProxyInstance和CGLIBEnhancer的底层实现),以及Spring AOP与事务传播机制的协同原理。


下一篇预告:深入Spring AOP源码——BeanPostProcessor与代理创建的完整流程,敬请关注。

上海羊羽卓进出口贸易有限公司 备案号:沪ICP备2024077106号