智能制造

Spring AOP 核心原理与实战(2026年4月9日)

小编 2026-04-28 智能制造 10 0

开篇引入

在Spring技术栈中,AOP(Aspect Oriented Programming,面向切面编程)与IoC并称为Spring两大核心基石,是每一位Java后端开发者必须掌握的知识点。许多初学者往往陷入“会用但不懂原理”的困境——知道@Before能拦截方法,却说不出它和@Around的本质区别;会配置切面,却答不上JDK动态代理和CGLIB的区别。AI助手科研资料,助你深入理解Spring AOP——本文将系统讲解Spring AOP的核心概念、注解使用、底层原理及高频面试考点,通过完整的代码示例带你从入门到进阶。

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

先看一个典型的业务场景——在每个Service方法中添加日志和性能监控:

java
复制
下载
@Service
public class UserService {
    public User getUserById(Long id) {
        System.out.println("【日志】开始执行getUserById,参数:" + id);
        long start = System.currentTimeMillis();
        // 业务逻辑...
        User user = userDao.findById(id);
        long end = System.currentTimeMillis();
        System.out.println("【性能】getUserById耗时:" + (end - start) + "ms");
        System.out.println("【日志】getUserById执行完毕,返回值:" + user);
        return user;
    }
    
    public void updateUser(User user) {
        System.out.println("【日志】开始执行updateUser,参数:" + user);
        long start = System.currentTimeMillis();
        // 业务逻辑...
        long end = System.currentTimeMillis();
        System.out.println("【性能】updateUser耗时:" + (end - start) + "ms");
        System.out.println("【日志】updateUser执行完毕");
    }
}

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

  • 代码冗余:日志、性能监控代码在每个方法中重复出现

  • 耦合度高:横切关注点(日志、性能)与核心业务逻辑耦合在一起

  • 维护困难:修改日志格式需要改动所有业务方法

  • 扩展性差:新增切面功能(如权限校验)要在每个方法上加代码

AOP正是为了解决这个问题而生——它将日志、权限、事务等“横切关注点”从业务逻辑中抽取出来,在运行期动态织入,实现代码的解耦和复用。

二、核心概念讲解

2.1 切面(Aspect)

标准定义:Aspect(切面)是封装横切关注点的模块化单元,包含多个通知和切点。简单理解,切面就是将分散在各处的共性功能(如日志、事务)集中封装成一个类-13

生活化类比:把切面想象成“安检员”。无论你进哪个商场,安检员都会在入口处拦截检查——安检逻辑本身独立于商场业务,但横切到了所有入口。

2.2 连接点(Join Point)

标准定义:Join Point(连接点)是程序执行过程中可以被插入切面逻辑的点。在Spring AOP中,连接点特指方法调用-13

2.3 通知(Advice)

标准定义:Advice(通知)是在特定连接点执行的动作,定义了切面“做什么”以及“何时做”-13。Spring AOP提供了五种通知类型:

通知类型注解执行时机
前置通知@Before目标方法执行之前
后置通知@After目标方法执行之后(无论是否异常)
返回通知@AfterReturning目标方法正常返回后
异常通知@AfterThrowing目标方法抛出异常后
环绕通知@Around包裹目标方法,可控制执行全过程

2.4 切点(Pointcut)

标准定义:Pointcut(切点)通过表达式匹配一组连接点,定义“何处”需要被切面处理-13。如果说通知定义了“何时做什么”,切点则定义了“在哪里做”-29

生活化类比:安检员只对“所有商场入口”执行检查——“所有商场入口”就是切点表达式,定义了切面应用的位置。

三、概念关系总结

概念作用一句话理解
切面(Aspect)封装横切关注点的模块装“增强代码”的盒子
连接点(Join Point)可插入切面的潜在位置能“下手”的所有地方
切点(Pointcut)筛选需要增强的连接点决定“对哪些地方下手”
通知(Advice)切面要执行的代码“下手之后怎么做”

一句话概括切点选位置 + 通知定行为 → 构成切面——切面通过切点筛选连接点,在通知中定义增强逻辑,最终织入目标对象-

四、代码示例:Spring Boot中的AOP实战

4.1 添加依赖

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

Spring Boot已为AOP提供了自动配置支持,引入依赖后即可直接使用-22

4.2 定义切面类

java
复制
下载
@Aspect           // 标记为切面类
@Component        // 纳入Spring容器管理
public class LoggingAspect {
    
    // 定义切点:匹配com.example.service包下所有类的所有方法
    @Pointcut("execution( com.example.service..(..))")
    public void serviceMethods() {}
    
    // 前置通知
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("方法执行前:" + joinPoint.getSignature().getName() 
            + ",参数:" + Arrays.toString(joinPoint.getArgs()));
    }
    
    // 后置通知
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("方法执行后:" + joinPoint.getSignature().getName());
    }
    
    // 返回通知(可访问返回值)
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("方法正常返回,结果:" + result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
        System.out.println("方法抛异常:" + e.getMessage());
    }
    
    // 环绕通知(可控制方法执行,最灵活)
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("【Around前置】开始执行:" + joinPoint.getSignature().getName());
        
        Object result = joinPoint.proceed();  // 执行目标方法
        
        long end = System.currentTimeMillis();
        System.out.println("【Around后置】执行完成,耗时:" + (end - start) + "ms");
        return result;
    }
}

4.3 执行效果

调用UserService.getUserById(1L)后,控制台输出:

text
复制
下载
【Around前置】开始执行:getUserById
方法执行前:getUserById,参数:[1]
执行目标方法...
方法正常返回,结果:User{id=1, name='张三'}
方法执行后:getUserById
【Around后置】执行完成,耗时:2ms

关键要点

  • @Aspect标记的类被Spring容器识别为切面,不会被动态代理,而是作为横切关注点织入目标对象-23

  • ProceedingJoinPointproceed()方法执行真正的业务逻辑,环绕通知可以控制是否执行、修改返回值等-22

  • 通知执行顺序:@Around前置 → @Before → 目标方法 → @AfterReturning/@AfterThrowing@After@Around后置

五、底层原理:动态代理

5.1 核心原理

Spring AOP的实现本质依赖于动态代理——通过创建目标对象的代理对象,在代理对象的方法调用前后插入切面逻辑-39

5.2 JDK动态代理 vs CGLIB

对比维度JDK动态代理CGLIB代理
实现条件目标类必须实现接口目标类无需接口
实现原理基于反射生成接口的实现类通过字节码技术生成目标类的子类
代理对象实现了目标接口的代理对象目标类的子类对象
限制仅代理接口中声明的方法final类/方法无法代理
Spring Boot默认使用CGLIB-

JDK动态代理:要求目标对象至少实现一个接口,通过Proxy.newProxyInstance()创建实现接口的代理对象,调用时经由InvocationHandler拦截-20

CGLIB代理:当目标类没有实现接口时,Spring会使用CGLIB。它通过ASM字节码生成框架创建目标类的子类,重写可重写的方法,在方法中织入切面逻辑-

5.3 Spring Boot中的默认行为

Spring Boot默认启用CGLIB代理,因此即使类实现了接口,也会优先使用CGLIB-43。这意味着:

  • 被代理的类不能是final类

  • 被增强的方法不能是final或private方法-43

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

Q1:什么是AOP?Spring AOP的底层原理是什么?

答案:AOP(面向切面编程)是一种编程范式,通过横向抽取机制将日志、事务等横切关注点从业务逻辑中分离,在不修改原有代码的前提下实现功能增强。

Spring AOP的底层基于动态代理

  • 若目标类实现接口,使用JDK动态代理(基于反射)

  • 若目标类无接口,使用CGLIB代理(基于字节码生成子类)

  • Spring Boot默认使用CGLIB代理-

踩分点:AOP定义 + 动态代理 + JDK/CGLIB对比 + 默认行为。

Q2:@Before@After@Around的区别?

通知类型执行时机特点
@Before目标方法执行前不能阻止方法执行
@After目标方法执行后(含异常)适合资源清理
@Around包裹目标方法可控制执行流程、修改返回值,功能最强

Q3:JDK动态代理和CGLIB代理有什么区别?Spring Boot默认用哪个?

答案

  • JDK动态代理:要求目标类实现接口,基于反射生成代理类,性能略好

  • CGLIB代理:无需接口,基于字节码生成子类,final类/方法无法代理

  • Spring Boot 默认使用CGLIB--

Q4:Spring AOP为什么只对public方法生效?内部自调用为什么无法被拦截?

答案

  • JDK动态代理只代理接口中声明的public方法

  • CGLIB通过继承子类重写方法,private/final方法无法重写

  • 内部自调用this.methodB())走的是原始对象引用,未经过代理对象,因此无法触发切面逻辑。解决方案:通过ApplicationContext.getBean()获取代理对象再调用-31

Q5:如何自定义注解实现AOP拦截?

答案:三步走——

① 定义注解:@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable {}
② 切面中引用:@Pointcut("@annotation(com.example.Loggable)")
③ 在目标方法上添加@Loggable注解即可-22

七、结尾总结

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

  1. 痛点驱动:日志、权限等横切关注点分散在业务代码中,导致冗余和耦合

  2. 四大核心概念:切面(Aspect) = 切点(Pointcut)选位置 + 通知(Advice)定行为

  3. 五种通知类型@Before@After@AfterReturning/@AfterThrowing@Around

  4. 底层原理:JDK动态代理(接口反射) vs CGLIB(字节码子类),Spring Boot默认CGLIB

  5. 常见陷阱:非public方法无法拦截 + 内部自调用绕过代理

重点记忆:切点选“在哪里做”,通知定“什么时候做什么”,两者结合构成切面。面试常考动态代理机制和默认行为,务必熟记。


下一篇预告:Spring AOP进阶——通知执行链路与责任链模式深度剖析

猜你喜欢