芯片中心

手写JDK动态代理与CGLIB,彻底搞懂Spring AOP底层原理|2026年4月更新

小编 2026-05-26 芯片中心 23 0

一、开篇引入

你是否遇到过这样的场景:项目中有十几个Service类,每个方法都需要记录日志、校验权限,结果代码里充斥着大量重复的“样板代码”?更让人头疼的是,明明加了@Transactional注解,事务却不生效,找了半天才发现是“内部调用”惹的祸。

AOP(Aspect-Oriented Programming,面向切面编程)正是为了解决这些痛点而生的编程范式。作为Spring框架的两大核心特性之一(与IoC并称Spring“双璧”),AOP在日志记录、事务管理、安全控制、性能监控等场景中无处不在,是每一位Java开发者的必学必考知识点。然而很多开发者的状态是:会用注解,但不懂原理;懂AOP,却说不清动态代理;知道JDK和CGLIB的区别,但面试时表述混乱

本文将围绕“AOP是什么 → 为什么需要它 → 核心概念 → 底层实现原理 → 代码实战 → 面试要点”这条主线,从痛点切入,手写最小可运行的动态代理示例,带你把AOP的底层机制彻底搞懂。

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

2.1 传统实现方式的问题

假设你有一个用户服务类,需要在每个方法前后添加日志记录功能:

java
复制
下载
public class UserService {
    
    public void saveUser(String username) {
        // 日志记录:方法开始
        System.out.println("[LOG] 开始执行 saveUser,参数:" + username);
        long startTime = System.currentTimeMillis();
        
        // 核心业务逻辑
        System.out.println("正在保存用户:" + username);
        
        // 日志记录:方法结束
        long endTime = System.currentTimeMillis();
        System.out.println("[LOG] saveUser 执行完成,耗时:" + (endTime - startTime) + "ms");
    }
    
    public void deleteUser(int userId) {
        System.out.println("[LOG] 开始执行 deleteUser,参数:" + userId);
        long startTime = System.currentTimeMillis();
        
        System.out.println("正在删除用户:" + userId);
        
        long endTime = System.currentTimeMillis();
        System.out.println("[LOG] deleteUser 执行完成,耗时:" + (endTime - startTime) + "ms");
    }
}

2.2 传统方式的四大缺陷

这种实现方式暴露了OOP在处理“横切关注点”时的局限性-11

  1. 代码冗余:日志、性能监控等横切关注点的代码在多个方法中重复编写,违反DRY原则-

  2. 耦合度高:核心业务逻辑与辅助功能(日志、监控)混杂在一起,任何一个修改都可能影响业务代码。

  3. 维护困难:当需要修改日志格式或添加新的横切逻辑时,必须逐个方法修改。

  4. 扩展性差:新增一个方法就要手动添加一遍样板代码,项目越大越痛苦。

代码纠缠代码分散——这是OOP在处理横切关注点时的根本问题-。AOP正是为了解决这些问题而生的。

三、核心概念讲解:AOP是什么

3.1 标准定义

AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,它将横切关注点(cross-cutting concerns)从核心业务逻辑中抽离出来,形成独立的模块(切面),通过“动态织入”的方式,在不修改原有业务代码的前提下为程序添加增强行为--2

3.2 关键词拆解

  • 横切关注点(Cross-cutting Concern) :那些影响应用程序多个模块的功能,如日志、事务、安全、缓存、性能监控等。这些功能“横切”于多个业务模块之上。

  • 切面(Aspect) :将横切关注点模块化后形成的独立单元,如“日志切面”“事务切面”。

  • 动态织入(Dynamic Weaving) :在程序运行时,将切面代码动态地“编织”到目标方法的前后或周围。

3.3 生活化类比

想象一家餐厅:

  • 厨师负责做菜(业务逻辑)

  • 服务员负责记录客人的忌口偏好(前置处理)

  • 收银员负责结账(后置处理)

  • 卫生检查则贯穿整个流程(横切关注点)

如果没有AOP,每位厨师在做菜时都需要自己检查卫生、记录客人要求、处理结账——业务逻辑被各种“杂事”塞满。AOP就像餐厅引入了一个专门的后勤保障体系,把所有“杂事”集中管理,厨师只需要安心做菜即可。

四、关联概念讲解:切面与动态代理

4.1 AOP核心术语(面试必考)

术语英文含义
切面Aspect横切关注点的模块化实现,如日志切面、事务切面
连接点Join Point程序执行过程中可插入切面的“时机点”,如方法调用前、异常抛出时
通知Advice切面在连接点上执行的具体动作
切点Pointcut通过表达式匹配一组连接点,决定哪些方法会被切面拦截
目标对象Target Object被切面增强的原始业务对象
代理对象Proxy织入切面逻辑后生成的代理对象,实际对外提供服务
织入Weaving将切面代码与目标对象关联并生成代理对象的过程

-5-52

4.2 五种通知类型(Advice)

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

-5

4.3 概念关系梳理

text
复制
下载
切面(Aspect)   —— 模块化单元,包含【切点 + 通知】
    ├── 切点(Pointcut) —— 定义“在哪些地方”织入
    └── 通知(Advice)   —— 定义“做什么”操作

一句话总结切面 = 切点 + 通知。切点决定“打哪里”,通知决定“怎么打”。

五、概念关系与区别总结

5.1 思想 vs 实现:AOP与动态代理的关系

维度AOP动态代理
定位编程思想/范式技术实现手段
作用定义“做什么”(抽离横切关注点)解决“怎么做”(运行时生成代理对象)
关系AOP的设计理念AOP的底层实现机制

💡 关键理解:AOP是一种编程思想(将横切关注点与业务逻辑分离),而动态代理是实现这一思想的技术手段。Spring AOP正是通过动态代理在运行时将切面逻辑织入目标对象-11

5.2 JDK动态代理 vs CGLIB动态代理

这是Spring AOP面试中的必考题

对比维度JDK动态代理CGLIB动态代理
实现原理基于接口,生成实现目标接口的代理类基于继承,通过字节码生成目标类的子类
目标类要求必须实现至少一个接口无需接口,但不能是final
方法要求无特殊限制无法代理final方法
性能特点反射调用开销,JDK 8+优化明显字节码生成有启动成本,运行时调用更快
Spring默认策略目标类有接口时优先使用目标类无接口时自动切换

-24-27

📌 版本注意:Spring Framework 3.2开始内置CGLIB,Spring Boot 2.x将默认值改为CGLIB-27

六、代码/流程示例

6.1 手写JDK动态代理(最小可运行版本)

JDK动态代理的核心是java.lang.reflect.Proxy类和InvocationHandler接口-

java
复制
下载
// Step 1:定义一个接口(JDK代理强制要求)
public interface UserService {
    void register();
}

// Step 2:实现类
public class UserServiceImpl implements UserService {
    @Override
    public void register() {
        System.out.println("【核心业务】正在执行用户注册...");
    }
}

// Step 3:实现InvocationHandler接口
import java.lang.reflect.;
public class LogInvocationHandler implements InvocationHandler {
    private Object target;  // 持有被代理对象的引用
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ⭐ 前置增强
        System.out.println("【前置通知】方法:" + method.getName() + " 开始执行");
        
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        
        // ⭐ 后置增强
        System.out.println("【后置通知】方法:" + method.getName() + " 执行完毕");
        return result;
    }
}

// Step 4:生成代理对象并使用
public class JdkProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        
        // 生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),      // 类加载器
            target.getClass().getInterfaces(),       // 目标对象实现的接口
            new LogInvocationHandler(target)         // InvocationHandler
        );
        
        // 调用代理对象的方法 → 自动触发增强逻辑
        proxy.register();
    }
}

执行输出

text
复制
下载
【前置通知】方法:register 开始执行
【核心业务】正在执行用户注册...
【后置通知】方法:register 执行完毕

6.2 手写CGLIB动态代理

CGLIB的核心是Enhancer类和MethodInterceptor接口--45

java
复制
下载
// Step 1:目标类(无需实现接口)
public class ProductService {
    public void create() {
        System.out.println("【核心业务】正在创建产品...");
    }
}

// Step 2:实现MethodInterceptor接口
import net.sf.cglib.proxy.;
public class LogMethodInterceptor implements MethodInterceptor {
    private Object target;
    
    public LogMethodInterceptor(Object target) {
        this.target = target;
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) 
            throws Throwable {
        // ⭐ 前置增强
        System.out.println("【前置通知】方法:" + method.getName() + " 开始执行");
        
        // 调用目标方法(注意:使用invokeSuper)
        Object result = proxy.invokeSuper(obj, args);
        
        // ⭐ 后置增强
        System.out.println("【后置通知】方法:" + method.getName() + " 执行完毕");
        return result;
    }
}

// Step 3:生成CGLIB代理对象
import net.sf.cglib.core.DebuggingClassWriter;
public class CglibProxyDemo {
    public static void main(String[] args) {
        // 可选:将生成的代理类输出到磁盘便于分析
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");
        
        ProductService target = new ProductService();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());      // 设置父类(目标类)
        enhancer.setCallback(new LogMethodInterceptor(target));  // 设置拦截器
        
        ProductService proxy = (ProductService) enhancer.create();
        proxy.create();
    }
}

6.3 Spring Boot中使用@Aspect注解(生产级)

java
复制
下载
// 步骤一:在pom.xml中添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

// 步骤二:定义切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.;
import org.springframework.stereotype.Component;

@Aspect          // 标记为切面类
@Component       // 交给Spring IoC容器管理
public class PerformanceAspect {
    
    // 定义切点:匹配service包下所有类的所有方法
    @Pointcut("execution( com.example.service..(..))")
    public void serviceMethods() {}
    
    // 环绕通知
    @Around("serviceMethods()")
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        // 执行目标方法(核心)
        Object result = joinPoint.proceed();
        
        long cost = System.currentTimeMillis() - start;
        System.out.println(joinPoint.getSignature() + " 耗时:" + cost + "ms");
        return result;
    }
}

-2-61

七、底层原理/技术支撑

7.1 两种代理机制的技术栈

JDK动态代理的技术基础

  • 反射(Reflection) :通过java.lang.reflect.Methodinvoke()方法动态调用目标方法。

  • Proxy类:运行时动态生成实现指定接口的代理类的字节码。

  • InvocationHandler:统一接管代理类中所有方法的调用。

CGLIB动态代理的技术基础

  • ASM字节码操作框架:运行时动态生成Java字节码-45

  • Enhancer:CGLIB的核心入口类,用于配置和生成代理类。

  • MethodInterceptor:拦截器接口,实现intercept()方法定义增强逻辑。

7.2 调用链路对比

JDK动态代理CGLIB动态代理
代理类生成Proxy.newProxyInstance()Enhancer.create()
调用流程代理类 → invoke() → 反射调用目标方法子类 → intercept() → 调用父类方法
方法调用方式method.invoke(target, args)proxy.invokeSuper(obj, args)

7.3 Spring AOP vs AspectJ

维度Spring AOPAspectJ
织入时机运行时动态代理编译时或类加载时织入
性能略有开销(运行时生成代理)更高(编译时已优化)
功能范围仅支持方法级别连接点支持字段、构造器、静态代码块等
适用场景轻量级应用,够用且简单企业级复杂切面需求

-5

💡 简单理解:Spring AOP是AspectJ的“轻量级平替”,用动态代理的“小成本”换来开发效率的“大提升”。

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

Q1:什么是AOP?它的核心思想是什么?

参考答案
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,核心思想是“将横切关注点与核心业务逻辑分离”。横切关注点指那些影响多个模块的通用功能(如日志、事务、权限)。AOP在不修改原有业务代码的前提下,通过“动态织入”的方式将这些功能统一应用到目标方法上,实现代码解耦和模块化-52-53

💡 踩分点:编程范式、横切关注点、动态织入、解耦

Q2:Spring AOP的底层实现原理是什么?JDK动态代理和CGLIB有什么区别?

参考答案
Spring AOP的底层基于动态代理模式实现,在运行时动态生成代理对象,将切面逻辑织入目标方法-21

两种代理方式的区别

  1. JDK动态代理:基于接口,要求目标对象实现至少一个接口,通过ProxyInvocationHandler生成代理类-52

  2. CGLIB动态代理:基于继承,通过ASM字节码框架生成目标类的子类,无需接口,但无法代理final类和方法-52

Spring的选择策略:目标类有接口时优先用JDK,无接口时自动切CGLIB;Spring Boot 2.x起默认使用CGLIB-27

💡 踩分点:动态代理、接口vs继承、反射vs字节码、Spring版本差异

Q3:@Around通知和@Before/@After通知有什么区别?

参考答案
核心区别在于能否控制目标方法的执行流程

  • @Before/@After等普通通知仅能在目标方法执行前后附加逻辑,无法阻止方法执行,也无法修改参数和返回值-52

  • @Around环绕通知通过ProceedingJoinPoint.proceed()手动控制目标方法的执行,可以实现:不调用proceed()则目标方法不执行;传入新参数修改入参;修改返回值;实现重试、熔断等复杂逻辑-52-53

💡 踩分点proceed()方法、完全控制、最强通知

Q4:为什么@Transactional有时会失效?列举常见原因。

参考答案(面试高频):

  1. 方法不是public:Spring事务只对public方法生效。

  2. 同一个类内部调用:A类中methodA调用methodB(带有@Transactional),调用走的是this引用而非代理对象,AOP不生效-53

  3. final方法:无法被CGLIB代理重写。

  4. 异常未被捕获:事务默认回滚RuntimeExceptionError,检查异常需指定rollbackFor

  5. 数据源未配置事务管理器

💡 踩分点:public限定、内部调用无代理、final限制

Q5:Spring AOP和AspectJ有什么区别?

参考答案

维度Spring AOPAspectJ
织入时机运行时(动态代理)编译时/类加载时
连接点范围仅方法级别方法、字段、构造器、静态代码块等
依赖纯Java,无需额外工具需AspectJ编译器或LTW
性能略有反射开销更高
典型场景日志、事务、权限(够用)更精细的代码增强

Spring AOP是AspectJ的轻量级替代方案,能满足大多数业务需求-5

九、结尾总结

9.1 核心知识点回顾

  1. AOP的本质:一种编程范式,将横切关注点从业务逻辑中抽离,实现关注点分离。

  2. 核心概念七剑客:切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)、目标对象(Target)、代理(Proxy)、织入(Weaving)。

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

  4. 底层原理:Spring AOP基于动态代理——JDK Proxy(接口代理)和CGLIB(继承代理)。

  5. 事务失效陷阱:public限定、内部调用无代理、final方法不可代理。

9.2 易错点提醒

⚠️ 高频踩坑:在同一个类的内部方法中调用另一个带@Transactional/@Cacheable的方法时,由于调用走的是this引用而非Spring代理对象,切面逻辑不会生效。解决办法:通过AopContext.currentProxy()获取代理对象后调用,或将该方法抽到另一个Bean中。

9.3 进阶预告

本文重点讲解了AOP的核心概念和动态代理原理,下一篇文章我们将深入Spring AOP的源码层面:

  • @EnableAspectJAutoProxy注解出发,追踪AOP代理的创建过程;

  • 分析AnnotationAwareAspectJAutoProxyCreator如何识别@Aspect切面;

  • 探讨通知的执行链路和责任链模式的应用。

如果觉得本文有帮助,欢迎点赞收藏,持续关注后续更新~

猜你喜欢