Java动态代理原理入门与面试话术汇总

 更新时间:2026年07月24日 11:21:05   作者:yoothey  
在Java开发中动态代理是一种强大的技术,它允许在运行时创建代理对象,用于对目标对象的方法进行增强,这篇文章主要介绍了Java动态代理原理入门与面试话术汇总的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一、什么是代理

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

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

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

二、静态代理 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 的底层原理

到此这篇关于Java动态代理原理入门与面试话术汇总的文章就介绍到这了,更多相关Java动态代理原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中实现代理方式

    SpringBoot中实现代理方式

    这篇文章主要介绍了SpringBoot中实现代理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • spring中ioc是什么

    spring中ioc是什么

    IoC是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。下面通过本文给大家分享spring中ioc的概念,感兴趣的朋友一起看看吧
    2017-09-09
  • Nacos快速安装部署教程

    Nacos快速安装部署教程

    文章简要介绍Nacos作为阿里巴巴开源的微服务管理组件,涵盖其核心功能及单机模式部署步骤:下载稳定版、配置MySQL、执行数据库脚本、启动服务并查看日志,最后通过指定地址和账号登录控制台
    2025-07-07
  • idea运行main方法或Test避免编译整个应用的实现方法

    idea运行main方法或Test避免编译整个应用的实现方法

    这篇文章主要介绍了idea运行main方法或Test避免编译整个应用的方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • 基于JDOM生成解析XML过程解析

    基于JDOM生成解析XML过程解析

    这篇文章主要介绍了基于JDOM生成解析XML过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java编程使用Runtime和Process类运行外部程序的方法

    Java编程使用Runtime和Process类运行外部程序的方法

    这篇文章主要介绍了Java编程使用Runtime和Process类运行外部程序的方法,结合实例形式分析了java使用Runtime.getRuntime().exec()方法运行外部程序的常见情况与操作技巧,需要的朋友可以参考下
    2017-08-08
  • Java怎样创建集合才能避免造成内存泄漏你了解吗

    Java怎样创建集合才能避免造成内存泄漏你了解吗

    内存泄漏是指无用对象持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收
    2021-09-09
  • Java中的volatile实现机制详细解析

    Java中的volatile实现机制详细解析

    这篇文章主要介绍了Java中的volatile实现机制详细解析,本文的主要内容就在于要理解volatile的缓存的一致性协议导致的共享变量可见性,以及volatile在解析成为汇编语言的时候对变量加锁两块理论内容,需要的朋友可以参考下
    2024-01-01
  • 一文教你如何更改IDEA已有项目的路径/名称

    一文教你如何更改IDEA已有项目的路径/名称

    由于IDEA项目路径中有中文、空格等特殊符号,影响正常使用,想要修改路径名称,怎么正确修改IDEA项目名称,使其正常运行呢?所以本文小编讲给大家详细的介绍了更改IDEA已有项目的路径/名称解决方案,需要的朋友可以参考下
    2023-11-11
  • Java实现添加条形码到PDF表格的方法详解

    Java实现添加条形码到PDF表格的方法详解

    条码的应用已深入生活和工作的方方面面。本文以操作PDF文件为例,介绍如何利用Java语言在编辑表格时,向单元格中添加条形码,感兴趣的可以学习一下
    2022-06-06

最新评论