首页 研发技术文章正文

得道AI助手:2026最新Spring AOP从入门到底层原理万字精讲

研发技术 2026年05月06日 08:45 4 小编

📅 2026年4月9日 更新 · 面向Spring 6.x / Spring Boot 3.x 主流版本

在Java后端开发体系中,Spring AOP与IoC并称为Spring框架的两大基石,是每一个Java开发者必须掌握的核心高频知识点。很多开发者在实际工作中遇到了不少困惑:只知道用@Transactional注解开启事务,却不明白它底层是怎么生效的;写好了切面却发现方法就是不触发,排查半天找不出原因;面试时被问到“JDK动态代理和CGLIB的区别”,只能支支吾吾说出“一个有接口一个没有”……本文将从痛点切入 → 概念讲解 → 关系梳理 → 代码实战 → 原理剖析 → 面试要点这条主线展开,带你彻底搞懂Spring AOP,从“会用”进阶到“懂原理、能排错、过面试”。

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

传统面向对象的困局

先来看一个典型的业务场景。假设你有一个UserService,里面包含创建用户、更新用户等方法,现在需要为这些方法加上日志记录权限校验。传统的OOP方式是这样写的:

java
复制
下载
@Service
public class UserService {
    public void createUser(String name, String email) {
        // ✅ 核心业务:创建用户
        userRepository.save(new User(name, email));
        
        // ❌ 横切关注点:日志
        System.out.println("【日志】用户创建: " + name);
        
        // ❌ 横切关注点:权限校验
        if (!SecurityContext.hasPermission("CREATE_USER")) {
            throw new AccessDeniedException();
        }
    }
    
    public void updateUser(Long id, String name) {
        // ✅ 核心业务:更新用户
        userRepository.update(id, name);
        
        // ❌ 横切关注点:日志
        System.out.println("【日志】用户更新: " + id);
        
        // ❌ 横切关注点:权限校验
        if (!SecurityContext.hasPermission("UPDATE_USER")) {
            throw new AccessDeniedException();
        }
    }
}

这种写法存在明显的缺陷:代码重复散落职责严重混乱维护成本高昂复用性极差-8

AOP的设计初衷

AOP(Aspect-Oriented Programming,面向切面编程) 正是为了解决横切关注点与核心业务逻辑的耦合问题而诞生的编程范式。它将日志、事务、安全、缓存等通用功能封装成独立的切面(Aspect) ,通过声明式方式在运行时动态织入(Weaving) 到业务方法中,实现功能的无侵入增强-8

💡 一句话总结:AOP让业务代码只关心业务,横切逻辑交给框架自动处理。

二、核心概念精讲

AOP的核心术语有五个,必须记牢:

术语英文解释示例
切面Aspect横切关注点的模块化单元,包含切点和通知@Aspect注解的LogAspect
通知/增强Advice切面在特定连接点上执行的具体动作@Before前置通知
连接点Join Point程序执行中可以插入通知的点,Spring AOP中指方法执行业务方法的调用
切点Pointcut匹配连接点的表达式,定义通知作用于哪些方法execution( com.example.service..(..))
织入Weaving将切面应用到目标对象并创建代理对象的过程Spring运行时动态代理织入
目标对象Target被代理的原始对象UserServiceImpl实例
代理对象ProxyAOP生成的包装对象JDK/CGLIB代理实例

通知的五种类型

java
复制
下载
// 1. @Before:方法执行前
// 2. @AfterReturning:方法正常返回后
// 3. @AfterThrowing:方法抛出异常后
// 4. @After:无论正常/异常都执行(类似finally)
// 5. @Around:环绕通知(最强大,可控制执行流程)

三、概念关系与区别总结

AOP vs OOP

OOP关注纵向的继承与封装,解决的是“是什么”的问题;而AOP关注横向的切面切入,解决的是“额外做什么”的问题-2。两者不是对立关系,而是互补关系——OOP是主体架构,AOP是横切增强。

Spring AOP vs AspectJ

对比维度Spring AOPAspectJ
实现方式基于动态代理(JDK/CGLIB)基于字节码增强(编译时/加载时织入)
织入时机运行时织入编译时/加载时/运行时
功能丰富度方法级拦截(轻量级)全功能AOP(字段、构造器等均可拦截)
性能有运行时反射开销编译时织入性能最高
学习成本低,与Spring无缝集成较高,需独立配置
适用范围Spring容器管理的Bean任意Java类

一句话概括:Spring AOP是基于动态代理的轻量级运行时AOP实现,AspectJ是功能更强大的全功能AOP框架-

四、代码实战:从零实现一个日志切面

1. 添加依赖(Maven)

xml
复制
下载
运行
<!-- Spring AOP核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.1.0</version>
</dependency>
<!-- AspectJ注解支持 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.21</version>
</dependency>

2. 启用AOP自动代理

java
复制
下载
@Configuration
@EnableAspectJAutoProxy  // 🔑 关键!开启基于注解的AOP支持
public class AppConfig {
    // Spring Boot环境下,@SpringBootApplication已隐含此注解
}

3. 定义切面类

java
复制
下载
@Component
@Aspect
public class LoggingAspect {
    
    // 定义可复用的切点表达式
    @Pointcut("execution( com.example.service...(..))")
    public void serviceMethod() {}
    
    // 前置通知
    @Before("serviceMethod()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("【前置】调用方法:" + methodName + ",参数:" + Arrays.toString(args));
    }
    
    // 后置通知(正常返回后)
    @AfterReturning(pointcut = "serviceMethod()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("【返回】方法:" + joinPoint.getSignature().getName() + 
                           ",返回值:" + result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "serviceMethod()", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        System.out.println("【异常】方法:" + joinPoint.getSignature().getName() + 
                           ",异常:" + ex.getMessage());
    }
    
    // 环绕通知(最强大,可控制执行流程)
    @Around("serviceMethod()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("【环绕-开始】" + pjp.getSignature());
        try {
            Object result = pjp.proceed();  // 执行目标方法
            long cost = System.currentTimeMillis() - start;
            System.out.println("【环绕-结束】" + pjp.getSignature() + 
                               ",耗时:" + cost + "ms,返回:" + result);
            return result;
        } catch (Exception e) {
            System.out.println("【环绕-异常】" + pjp.getSignature() + ",异常:" + e);
            throw e;
        }
    }
}

💡 关键点

  • @Aspect声明这是一个切面类,但必须加上@Component让Spring容器管理,否则不会生效-

  • @Pointcut定义了匹配规则,可被多个通知复用

  • ProceedingJoinPoint仅在@Around通知中可用,它提供了proceed()方法来控制目标方法的执行

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

整体流程

Spring AOP的实质是:在IoC容器创建Bean的过程中,根据切面规则为目标Bean生成一个代理对象,并将所有通知编织成拦截器链,在代理执行目标方法时被逐一唤醒-30

核心组件与流程

图表
代码
下载
全屏
.kvfysmfp{overflow:hidden;touch-action:none}.ufhsfnkm{transform-origin: 0 0}
mermaid-svg-2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}mermaid-svg-2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}mermaid-svg-2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}mermaid-svg-2 .error-icon{fill:552222;}mermaid-svg-2 .error-text{fill:552222;stroke:552222;}mermaid-svg-2 .edge-thickness-normal{stroke-width:1px;}mermaid-svg-2 .edge-thickness-thick{stroke-width:3.5px;}mermaid-svg-2 .edge-pattern-solid{stroke-dasharray:0;}mermaid-svg-2 .edge-thickness-invisible{stroke-width:0;fill:none;}mermaid-svg-2 .edge-pattern-dashed{stroke-dasharray:3;}mermaid-svg-2 .edge-pattern-dotted{stroke-dasharray:2;}mermaid-svg-2 .marker{fill:333333;stroke:333333;}mermaid-svg-2 .marker.cross{stroke:333333;}mermaid-svg-2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}mermaid-svg-2 p{margin:0;}mermaid-svg-2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:333;}mermaid-svg-2 .cluster-label text{fill:333;}mermaid-svg-2 .cluster-label span{color:333;}mermaid-svg-2 .cluster-label span p{background-color:transparent;}mermaid-svg-2 .label text,mermaid-svg-2 span{fill:333;color:333;}mermaid-svg-2 .node rect,mermaid-svg-2 .node circle,mermaid-svg-2 .node ellipse,mermaid-svg-2 .node polygon,mermaid-svg-2 .node path{fill:ECECFF;stroke:9370DB;stroke-width:1px;}mermaid-svg-2 .rough-node .label text,mermaid-svg-2 .node .label text,mermaid-svg-2 .image-shape .label,mermaid-svg-2 .icon-shape .label{text-anchor:middle;}mermaid-svg-2 .node .katex path{fill:000;stroke:000;stroke-width:1px;}mermaid-svg-2 .rough-node .label,mermaid-svg-2 .node .label,mermaid-svg-2 .image-shape .label,mermaid-svg-2 .icon-shape .label{text-align:center;}mermaid-svg-2 .node.clickable{cursor:pointer;}mermaid-svg-2 .root .anchor path{fill:333333!important;stroke-width:0;stroke:333333;}mermaid-svg-2 .arrowheadPath{fill:333333;}mermaid-svg-2 .edgePath .path{stroke:333333;stroke-width:2.0px;}mermaid-svg-2 .flowchart-link{stroke:333333;fill:none;}mermaid-svg-2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}mermaid-svg-2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}mermaid-svg-2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}mermaid-svg-2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}mermaid-svg-2 .cluster rect{fill:ffffde;stroke:aaaa33;stroke-width:1px;}mermaid-svg-2 .cluster text{fill:333;}mermaid-svg-2 .cluster span{color:333;}mermaid-svg-2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid aaaa33;border-radius:2px;pointer-events:none;z-index:100;}mermaid-svg-2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:333;}mermaid-svg-2 rect.text{fill:none;stroke-width:0;}mermaid-svg-2 .icon-shape,mermaid-svg-2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}mermaid-svg-2 .icon-shape p,mermaid-svg-2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}mermaid-svg-2 .icon-shape rect,mermaid-svg-2 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}mermaid-svg-2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}mermaid-svg-2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}mermaid-svg-2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

有接口

无接口

@EnableAspectJAutoProxy

注册AnnotationAwareAspectJAutoProxyCreator

实现BeanPostProcessor接口

postProcessAfterInitialization

是否匹配切点?

返回原始Bean

创建代理对象ProxyFactory

选择代理策略

JDK动态代理

CGLIB代理

返回代理对象替换原始Bean

步骤拆解

① 启动入口:@EnableAspectJAutoProxy

该注解触发AspectJAutoProxyRegistrar,最终在容器中注册一个AnnotationAwareAspectJAutoProxyCreator——这是Spring AOP的核心组件,负责完成切面的织入工作-27

② 核心组件:AnnotationAwareAspectJAutoProxyCreator

它实现了BeanPostProcessor接口,在Spring IoC容器创建每个Bean的初始化后阶段进行拦截-30

③ 代理触发:postProcessAfterInitialization

java
复制
下载
// AbstractAutoProxyCreator源码(Spring 5.3.x)
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 核心:判断是否需要包装成代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

④ 决策与创建:wrapIfNecessarycreateProxy

  • 调用getAdvicesAndAdvisorsForBean,扫描所有@Aspect切面类,根据切点表达式判断是否匹配当前Bean-27

  • 若有匹配,通过ProxyFactory创建代理对象,选择JDK动态代理或CGLIB代理,最终返回代理对象替换原始Bean实例-30

JDK动态代理 vs CGLIB 深度对比

对比维度JDK动态代理CGLIB代理
代理方式接口代理(java.lang.reflect.Proxy子类代理(字节码生成)
是否依赖接口必须有接口不需要接口
适用场景目标类实现了接口目标类无接口,或强制指定
代理对象类型实现了目标接口的新类($Proxy0目标类的子类($$EnhancerBySpringCGLIB
final类/方法❌ 不可代理❌ 不可代理
性能特点反射调用,动态代理创建快生成字节码较慢,但方法调用性能更高
Spring默认策略有接口时默认使用无接口时自动fallback
外部依赖JDK原生,无需额外依赖cglib-nodep依赖(Spring Boot已内置)

💡 Spring的选择策略:有接口 → JDK动态代理;无接口 → CGLIB代理。可通过@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB-22-23

六、高频面试题

Q1:请解释一下Spring AOP的运行原理和动态代理实现方式

参考答案:

Spring AOP基于动态代理实现。在IoC容器初始化Bean时,通过BeanPostProcessor(具体是AnnotationAwareAspectJAutoProxyCreator)在Bean初始化完成后判断是否需要代理:若匹配切点表达式,则创建代理对象替换原始Bean。

代理方式分两种:

  • JDK动态代理:目标类有接口时使用,基于java.lang.reflect.Proxy生成实现了接口的代理类

  • CGLIB代理:目标类无接口时使用,通过字节码技术生成目标类的子类作为代理

Spring默认策略:有接口→JDK,无接口→CGLIB。可通过@EnableAspectJAutoProxy(proxyTargetClass=true)强制使用CGLIB-38

Q2:JDK动态代理和CGLIB有什么区别?

参考答案:

区别点JDK动态代理CGLIB
实现原理基于接口,生成实现了目标接口的代理类基于继承,生成目标类的子类
接口要求目标类必须实现至少一个接口无需接口
代理范围仅代理接口中声明的方法可代理类的所有非final、非static方法
final限制不适用(基于接口)无法代理final类/方法
性能代理创建快,方法调用有反射开销代理创建较慢,方法调用性能更高
依赖JDK原生需要cglib库

Q3:@Transactional注解为什么默认只在public方法上生效?

参考答案:

Spring AOP基于动态代理实现,代理对象只能拦截通过代理对象发起的调用。private/protected方法无法被代理拦截,因为代理子类无法重写父类的private方法,JDK代理也只能代理接口中的public方法。另外,同类内部调用(this.method() 也不会触发切面,因为此时调用的是目标对象自身的方法,没有经过代理对象-21

Q4:AOP失效的常见场景有哪些?如何解决?

参考答案:

常见失效场景:

  1. 同类内部方法调用this.method()不经过代理对象

  2. 方法非public:代理无法拦截private/protected方法

  3. 切点表达式不匹配:JDK代理下代理对象类型是接口,切点需按接口路径写

  4. 切面未被Spring管理@Aspect类未加@Component或未显式注册

  5. 切面执行顺序混乱:未使用@Order指定顺序

解决方案:

  • 内部调用:通过AopContext.currentProxy()获取代理对象再调用,或注入自身Bean

  • 非public方法:改为public

  • 切点表达式:仔细检查路径,JDK代理时使用接口路径

  • 切面管理:确保切面类被Spring容器管理

  • 执行顺序:使用@Order注解或实现Ordered接口-21

七、结尾总结

核心知识点回顾

模块核心要点
AOP是什么面向切面编程,解决横切关注点与业务逻辑的耦合问题
核心术语Aspect、Advice、Join Point、Pointcut、Weaving
通知类型@Before、@After、@AfterReturning、@AfterThrowing、@Around
底层实现动态代理(JDK动态代理 + CGLIB)
代理选择策略有接口→JDK,无接口→CGLIB
失效场景同类内部调用、非public方法、切点不匹配、切面未托管

学习建议

  • 面试重点:动态代理的区别与选择策略、AOP失效场景、核心术语的定义

  • 实战重点:掌握@Around环绕通知的使用,它是功能最强大、最灵活的通知类型

  • 排错技能:能用AopUtils.isAopProxy()等方法判断代理类型和排查失效问题-21

进阶预告

下一篇文章将深入剖析Spring AOP源码级执行流程——从ProxyFactory的代理创建细节,到ReflectiveMethodInvocation拦截器链的调用机制,再到@EnableAspectJAutoProxy的完整装配流程。敬请期待!

📌 参考与延伸

  • Spring官方文档:AOP章节(Spring 6.x)

  • 《Spring实战》第6版:AOP核心章节

  • AspectJ官方文档:AOP全功能实现参考

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