当前位置: 首页 > news >正文

Java 动态代理原理入门与面试

目录

一、什么是代理

二、静态代理 vs 动态代理

2.1 静态代理(编译时就确定代理谁)

三、动态代理的两种实现

四、JDK 动态代理(MyBatis 用的就是这个)

4.1 核心类

4.2 手写一个 JDK 动态代理

4.3 每一步发生了什么

4.4 为什么 MyBatis 用的是 JDK 动态代理

五、CGLIB 动态代理(Spring AOP 底层用的就是这个)

5.1 核心原理

5.2 手写一个 CGLIB 代理

六、JDK 代理 vs CGLIB 代理的代码对比

七、Spring AOP 和动态代理的关系

八、MyBatis + Spring AOP 的动态代理对比

九、面试话术汇总

Q1:什么是动态代理?和静态代理的区别?

Q2:JDK 动态代理的原理?

Q3:CGLIB 动态代理的原理?

Q4:Spring 默认用哪种动态代理?

Q5:MyBatis 的 Mapper 为什么用 JDK 动态代理?

Q6:动态代理有什么局限性?

十、一张图看完整个知识体系

十一、一句话速记


一、什么是代理

代理 = 中介 / 帮你办事的人

你不想亲自做一件事,找个人帮你做,但你还是能控制他做什么、怎么做。

你(目标对象)→ 代理(中介)→ 实际办事 ↑ 你可以在这里加额外操作(记录日志、权限校验、事务管理)

二、静态代理 vs 动态代理

2.1 静态代理(编译时就确定代理谁)

// 1. 定义接口 public interface UserService { void saveUser(String name); void deleteUser(Long id); } ​ // 2. 真正的实现类 public class UserServiceImpl implements UserService { @Override public void saveUser(String name) { System.out.println("保存用户:" + name); } ​ @Override public void deleteUser(Long id) { System.out.println("删除用户:" + id); } } ​ // 3. 代理类(需要手动写,代理谁就写谁的代理) public class UserServiceProxy implements UserService { private UserService target; // 持有真正的实现类 ​ public UserServiceProxy(UserService target) { this.target = target; } ​ @Override public void saveUser(String name) { System.out.println("【代理】开始执行"); long start = System.currentTimeMillis(); target.saveUser(name); // 调用真正的实现 long cost = System.currentTimeMillis() - start; System.out.println("【代理】执行完成,耗时 " + cost + "ms"); } ​ @Override public void deleteUser(Long id) { System.out.println("【代理】开始执行"); target.deleteUser(id); System.out.println("【代理】执行完成"); } } ​ // 4. 使用 public class Main { public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = new UserServiceProxy(target); proxy.saveUser("张三"); } }

静态代理的问题

有 10 个接口 → 要写 10 个代理类 有 100 个接口 → 要写 100 个代理类 太累了

动态代理解决这个问题:不需要手写代理类,运行时自动生成。


三、动态代理的两种实现

JDK 动态代理CGLIB 动态代理
要求目标类必须实现接口不需要接口,只要不是 final 类
原理基于java.lang.reflect.Proxy基于字节码生成子类(ASM)
性能反射调用,稍慢字节码生成,稍快
Spring 默认目标类有接口时用 JDK 代理目标类没接口时用 CGLIB
限制只能代理接口方法不能代理 final 类/方法

Spring Boot 2.x 默认全部用 CGLIB(即使有接口也用 CGLIB,因为性能更好)。


四、JDK 动态代理(MyBatis 用的就是这个)

4.1 核心类

java.lang.reflect.Proxy(创建代理对象) java.lang.reflect.InvocationHandler(拦截调用的处理器)

4.2 手写一个 JDK 动态代理

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; ​ public class JdkProxyDemo { ​ public static void main(String[] args) { ​ // 1. 创建目标对象 UserService target = new UserServiceImpl(); ​ // 2. 创建代理对象 UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 类加载器 target.getClass().getInterfaces(), // 要代理的接口 new MyInvocationHandler(target) // 调用处理器 ); ​ // 3. 调用代理对象的方法 proxy.saveUser("张三"); proxy.deleteUser(1L); } } ​ // 3. 定义调用处理器(核心!拦截所有方法调用) class MyInvocationHandler implements InvocationHandler { ​ private Object target; // 被代理的目标对象 ​ public MyInvocationHandler(Object target) { this.target = target; } ​ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ​ // --- 代理逻辑开始 --- String methodName = method.getName(); System.out.println("【代理】方法开始:" + methodName); long start = System.currentTimeMillis(); // --- 代理逻辑结束 --- ​ // 调用真正的目标方法 Object result = method.invoke(target, args); ​ // --- 代理逻辑开始 --- long cost = System.currentTimeMillis() - start; System.out.println("【代理】方法结束:" + methodName + ",耗时 " + cost + "ms"); // --- 代理逻辑结束 --- ​ return result; } }

运行结果

【代理】方法开始:saveUser 保存用户:张三 【代理】方法结束:saveUser,耗时 0ms 【代理】方法开始:deleteUser 删除用户:1 【代理】方法结束:deleteUser,耗时 0ms

4.3 每一步发生了什么

Proxy.newProxyInstance() 做了什么? │ ▼ ① 根据 ClassLoader + Interface[] 动态生成一个新类 │ 这个类实现了 UserService 接口 │ 但它的方法实现全部委托给 InvocationHandler │ ▼ ② 生成的类大致长这样(你不需要写,JVM 自动帮你生成): │ │ class $Proxy0 implements UserService { │ InvocationHandler h; │ │ public void saveUser(String name) { │ // 通过反射拿到方法对象 │ Method m = UserService.class.getMethod("saveUser", String.class); │ // 交给 InvocationHandler 处理 │ h.invoke(this, m, new Object[]{name}); │ } │ │ public void deleteUser(Long id) { │ Method m = UserService.class.getMethod("deleteUser", Long.class); │ h.invoke(this, m, new Object[]{id}); │ } │ } │ ▼ ③ new $Proxy0() → 返回代理对象

4.4 为什么 MyBatis 用的是 JDK 动态代理

// MyBatis 的 Mapper 是接口 public interface UserMapper { User selectById(Long id); } ​ // JDK 动态代理要求:目标必须实现接口 ✅ // Mapper 恰好是接口 → 天然适合 JDK 动态代理 ​ // MyBatis 内部大致是这样的 Object mapperProxy = Proxy.newProxyInstance( UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new MapperProxy(sqlSession) // 拦截器 ); // 拿到的 mapperProxy 就是你可以直接用的 "Mapper 实现"

五、CGLIB 动态代理(Spring AOP 底层用的就是这个)

5.1 核心原理

不需要接口,通过 ASM 字节码框架生成目标类的子类,重写父类方法来实现拦截。

目标类:UserServiceImpl(普通类,没实现接口) │ ▼ CGLIB 生成子类:UserServiceImpl$$EnhancerBySpringCGLIB │ 这个子类继承了 UserServiceImpl │ 重写了所有非 final 的 public 方法 │ ▼ 调用子类的方法时 → 拦截 → 转发给 MethodInterceptor

5.2 手写一个 CGLIB 代理

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyDemo { public static void main(String[] args) { // 1. 创建 CGLIB 代理 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); // 父类是谁 enhancer.setCallback(new MyMethodInterceptor()); // 拦截器 // 2. 创建代理对象(子类) UserServiceImpl proxy = (UserServiceImpl) enhancer.create(); // 3. 调用 proxy.saveUser("张三"); } } // 拦截器(类似 JDK 的 InvocationHandler) class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("【CGLIB 代理】方法开始:" + method.getName()); long start = System.currentTimeMillis(); // 调用父类的方法(不是 method.invoke) Object result = methodProxy.invokeSuper(obj, args); long cost = System.currentTimeMillis() - start; System.out.println("【CGLIB 代理】方法结束:" + method.getName() + ",耗时 " + cost + "ms"); return result; } }

注意:CGLIB 调用父类方法用methodProxy.invokeSuper(obj, args),而不是method.invoke()(那会死循环调自己)。


六、JDK 代理 vs CGLIB 代理的代码对比

// ========== JDK 动态代理 ========== // 1. 目标必须有接口 public interface UserService { void save(String name); } public class UserServiceImpl implements UserService { ... } // 2. 用 Proxy.newProxyInstance Object proxy = Proxy.newProxyInstance( classLoader, new Class[]{UserService.class}, // 接口 new InvocationHandler() { public Object invoke(Object p, Method m, Object[] args) { // 拦截逻辑 return m.invoke(target, args); // 调目标方法 } } ); // ========== CGLIB 动态代理 ========== // 1. 目标可以是普通类(不需要接口) public class UserService { public void save(String name) { ... } } // 2. 用 Enhancer Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); // 父类 enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object obj, Method m, Object[] args, MethodProxy mp) { // 拦截逻辑 return mp.invokeSuper(obj, args); // 调父类方法 } }); Object proxy = enhancer.create();

七、Spring AOP 和动态代理的关系

Spring AOP 的底层就是动态代理。

@Service public class UserService { @Transactional // Spring AOP 拦截这个方法 public void saveUser(String name) { // 你的业务代码 userMapper.insert(name); } }

Spring 怎么拦截的?

Spring 启动 │ ▼ 发现 UserService 有 @Transactional │ ▼ 用动态代理包装 UserService │ 有接口 → JDK 动态代理(或 CGLIB,看配置) │ 没接口 → CGLIB 动态代理 │ ▼ 你注入的 UserService 是代理对象 │ ▼ 调用 saveUser() 时 │ 代理拦截 → 开启事务 → 执行你的方法 → 提交/回滚事务
// 你写的代码(看起来像直接调) userService.saveUser("张三"); // 实际执行的(代理拦截后) @Transactional 开启 → userService.saveUser("张三") // 真正的业务方法 @Transactional 提交

所以 Spring AOP = 动态代理 + 切面逻辑。


八、MyBatis + Spring AOP 的动态代理对比

MyBatis MapperSpring AOP
代理谁Mapper 接口Service/Controller 等 Bean
代理方式JDK 动态代理(Mapper 是接口)有接口用 JDK,没接口用 CGLIB
拦截器MapperProxy(找 SQL、执行)TransactionInterceptor(事务管理)等
用途把接口调用转成 SQL 执行日志、事务、权限、缓存等切面功能
创建时机Spring 启动时Spring 启动时
拦截时机每次调用 Mapper 方法时每次调用 Bean 方法时

九、面试话术汇总

Q1:什么是动态代理?和静态代理的区别?

话术:动态代理不需要手写代理类,在运行时通过反射或字节码技术自动生成。静态代理每个目标类都要写一个代理类,10 个接口写 10 个,维护成本高。动态代理只需要一个处理器,所有方法调用都走同一个处理器,通用性强。JDK 动态代理基于反射,CGLIB 基于字节码生成子类。

Q2:JDK 动态代理的原理?

话术:JDK 动态代理通过 Proxy.newProxyInstance() 方法,传入目标类的接口和一个 InvocationHandler 处理器,JVM 在运行时动态生成一个实现了目标接口的代理类。调用代理对象的方法时,所有调用都会被路由到 InvocationHandler 的 invoke() 方法,在这里可以插入额外逻辑,再通过反射调用目标对象的真正方法。

Q3:CGLIB 动态代理的原理?

话术:CGLIB 通过 ASM 字节码框架,运行时生成目标类的子类,重写所有非 final 的 public 方法。调用子类方法时,拦截交给 MethodInterceptor,再调用父类的原始方法。因为是生成子类,所以不需要接口,但不能代理 final 类和 final 方法。Spring Boot 2.x 默认全部用 CGLIB,即使有接口也用,因为性能比 JDK 反射调用更好。

Q4:Spring 默认用哪种动态代理?

话术:Spring Boot 2.x 之前,默认规则是目标类有接口用 JDK 动态代理,没有接口用 CGLIB。Spring Boot 2.x 之后,默认全部用 CGLIB,因为 CGLIB 性能更好。可以通过 spring.aop.proxy-target-class=false 强制用 JDK 代理。

Q5:MyBatis 的 Mapper 为什么用 JDK 动态代理?

话术:因为 Mapper 是接口,JDK 动态代理要求目标必须有接口,正好匹配。MyBatis 用 Proxy.newProxyInstance() 创建代理对象,InvocationHandler 是 MapperProxy,里面做了三件事:根据方法名找 SQL、通过 SqlSession 执行、结果映射成 Java 对象。

Q6:动态代理有什么局限性?

话术:两个局限。JDK 动态代理只能代理接口方法,不能代理非接口方法;CGLIB 不能代理 final 类和 final 方法(因为是生成子类,final 不能被重写)。性能上,JDK 代理通过反射调用,CGLIB 通过字节码生成方法直接调用,CGLIB 首次生成稍慢但后续调用更快。另外动态代理会增加调试难度,因为堆栈里多了一层代理类。


十、一张图看完整个知识体系

动态代理 ├── 静态代理(手写代理类,不灵活) │ ├── JDK 动态代理(基于接口) │ ├── Proxy.newProxyInstance() │ ├── InvocationHandler.invoke() │ └── 应用:MyBatis Mapper、Spring AOP(有接口时) │ └── CGLIB 动态代理(基于子类) ├── Enhancer + MethodInterceptor ├── ASM 字节码生成子类 └── 应用:Spring AOP(无接口时 / Spring Boot 2.x 全部用)

十一、一句话速记

代理 = 中介,帮你干活还能加额外操作 JDK 代理 = 基于接口,Proxy + InvocationHandler,反射调用 CGLIB 代理 = 基于子类,Enhancer + MethodInterceptor,字节码生成 MyBatis 用 JDK 代理(Mapper 是接口) Spring Boot 2.x 默认全用 CGLIB(性能更好) 动态代理 = Spring AOP 的底层原理
http://www.jsqmd.com/news/1100375/

相关文章:

  • 黄金目前仍有下调压力
  • 原神玩家数据查询:3分钟掌握账号完整信息的终极工具
  • 单身证明公证书需要什么材料?单身证明公证书在哪里办?
  • MySQL数据库零基础入门:从环境搭建到CRUD实战完整指南
  • 自部署GLM-5.2模型实战:如何超越官方API的响应速度与成本效益
  • Loop Engineering: A Systematic Survey of Agentic AI Engineering Paradigms and Practices
  • 【每天认识一个国家 | 塞内加尔】
  • Android应用安全:为什么必须关闭allowBackup属性以防止数据泄露
  • 【C++】移动语义和完美转发
  • Selenium Web自动化测试:从核心原理到企业级框架实战
  • Kali Linux下从零构建远程控制程序:理解C/S架构与安全攻防原理
  • N_m3u8DL-RE技术深度解析:现代流媒体下载架构实现
  • 3分钟快速上手:终极免费暗黑2存档编辑器的完整指南
  • 冷轧薄板用校平机:为什么这类材料对矫平精度要求最高?
  • 【AWS】基于Docker搭建监控系统基础(二)
  • 手把手教你用QRC提取RC寄生参数:从.cmd文件配置到SPEF输出的完整避坑指南
  • TEA系列加密算法实战:从C到Python的跨平台轻量级实现
  • 2026年,AI搜索优化的技术底层:从向量检索到商品卡交易闭环,每一层到底在做什么
  • 别再踩坑了!用Python控制Agilent 34401A万用表,这个SYSTEM:REMOTE命令必须发
  • ESP32驱动S90舵机保姆级教程:从PWM原理到库函数封装,附完整代码
  • 终极英雄联盟效率工具:5分钟提升游戏表现的完整指南
  • AI驱动边界值测试实战:从原理到发现三大隐藏Bug
  • 保姆级教程:在Ubuntu 22.04上搞定USRP B200/B210与GNURadio 3.10的连接测试
  • AI赋能Nmap:构建智能安全扫描与自动化风险分析系统
  • 2026好用的视频去水印工具:电脑手机免费付费、在线网站全推荐
  • 高端机自动发评论速度记录
  • 长尾关键词在SEO优先策略中的有效应用与成效分析
  • 专业流媒体下载方案:N_m3u8DL-RE实现DASH/HLS/MSS内容高效保存
  • 如何一键永久保存你的微信记忆?WeChatMsg完全免费解决方案揭秘
  • Web Crypto API实战:AES-CBC加密逆向分析与Node.js复现