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什么是动态代理和静态代理的区别Q2JDK 动态代理的原理Q3CGLIB 动态代理的原理Q4Spring 默认用哪种动态代理Q5MyBatis 的 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耗时 0ms4.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 方法 │ ▼ 调用子类的方法时 → 拦截 → 转发给 MethodInterceptor5.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 基于字节码生成子类。Q2JDK 动态代理的原理话术JDK 动态代理通过 Proxy.newProxyInstance() 方法传入目标类的接口和一个 InvocationHandler 处理器JVM 在运行时动态生成一个实现了目标接口的代理类。调用代理对象的方法时所有调用都会被路由到 InvocationHandler 的 invoke() 方法在这里可以插入额外逻辑再通过反射调用目标对象的真正方法。Q3CGLIB 动态代理的原理话术CGLIB 通过 ASM 字节码框架运行时生成目标类的子类重写所有非 final 的 public 方法。调用子类方法时拦截交给 MethodInterceptor再调用父类的原始方法。因为是生成子类所以不需要接口但不能代理 final 类和 final 方法。Spring Boot 2.x 默认全部用 CGLIB即使有接口也用因为性能比 JDK 反射调用更好。Q4Spring 默认用哪种动态代理话术Spring Boot 2.x 之前默认规则是目标类有接口用 JDK 动态代理没有接口用 CGLIB。Spring Boot 2.x 之后默认全部用 CGLIB因为 CGLIB 性能更好。可以通过 spring.aop.proxy-target-classfalse 强制用 JDK 代理。Q5MyBatis 的 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 的底层原理