首页 维修项目文章正文

一文看懂Java代理模式:静态代理、JDK动态代理与CGLIB动态代理全解析|2026年4月8日

维修项目 2026年04月29日 09:24 1 小编

松鼠AI助手带你彻底搞懂Java代理模式三种实现方式

本文首发于 2026 年 4 月 8 日,松鼠 AI 助手与你一起深入探讨代理模式这一 Java 开发中的核心设计模式,从静态代理到动态代理,从底层原理到面试考点,全方位打通你的知识链路。

一、开篇引入:为什么你必须掌握代理模式?

代理模式是 Java 设计模式中最基础也最重要的模式之一,更是 Spring AOP(面向切面编程,Aspect-Oriented Programming) 的底层基石-47。无论是日志记录、事务管理、权限校验,还是性能监控,代理模式都在背后默默发挥着“中间层”的关键作用。

许多学习者在接触代理模式时常陷入以下困境:

  • 只会用,不懂原理——知道 Spring AOP 怎么配,但说不出底层实现机制;

  • 概念混淆——静态代理、JDK 动态代理、CGLIB 动态代理傻傻分不清;

  • 面试答不出——被问到“JDK 和 CGLIB 有什么区别”时只能支支吾吾。

本文将沿着 “痛点 → 概念 → 关系 → 示例 → 原理 → 考点” 的逻辑链路,由浅入深地带你吃透 Java 代理模式,从手写静态代理到深入动态代理的底层实现,再串联面试高频考点,帮你建立完整知识体系。

📌 预告:本文为“代理模式系列”的第一篇,后续将深入 Spring AOP 源码级别的实现分析,敬请关注。

二、痛点切入:传统方式的“硬编码”困境

在学习代理模式之前,我们先来看一个典型的开发痛点。

2.1 传统实现方式

假设我们需要给一个任务执行方法添加执行时间统计功能,传统的做法是直接将统计逻辑写在业务代码里:

java
复制
下载
public class TaskService {
    public void dealTask(String taskName) {
        // 非业务逻辑:记录开始时间
        long startTime = System.currentTimeMillis();
        
        // 核心业务逻辑
        System.out.println("执行任务:" + taskName);
        try { Thread.sleep(500); } catch (InterruptedException e) {}
        
        // 非业务逻辑:计算执行时间
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("任务耗时:" + cost + "ms");
    }
}

2.2 传统方式的痛点分析

上述写法存在三大严重问题-54

痛点具体表现
耦合度高统计逻辑与业务逻辑混杂,修改统计规则需要改动业务代码,违反开闭原则
代码冗余若有 10 个方法需要统计时间,需要重复编写 10 遍时间记录代码
职责混乱一个方法同时负责“业务执行”和“性能统计”,不符合单一职责原则

2.3 代理模式的解决思路

代理模式通过引入 “代理对象” 作为中间层,实现“业务逻辑”与“增强功能”的彻底解耦-54。其核心结构包含三层:

text
复制
下载
抽象接口 —— 定义统一行为规范

目标对象 —— 专注核心业务逻辑

代理对象 —— 持有目标对象引用,在调用前后附加增强逻辑

这样一来,增强功能不改业务代码、新增增强只需加代理,完美解决了上述痛点。

三、静态代理(Static Proxy):最基础的实现方式

3.1 概念定义

静态代理是指在程序编译时就已经确定了代理类和被代理类的关系。代理类需要实现与被代理类相同的接口,并在代理类中持有被代理类的实例,通过调用其方法实现功能增强-2

3.2 代码示例

下面通过一个“房屋中介”的场景来演示静态代理的完整实现:

步骤1:定义抽象接口

java
复制
下载
// 抽象主题:定义统一行为规范
public interface HouseSubject {
    void rentHouse();   // 房屋租赁业务
    void saleHouse();   // 房屋出售业务
}

步骤2:实现目标对象(真实业务)

java
复制
下载
// 真实主题:业主(专注核心业务逻辑)
public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("业主执行房屋租赁流程:签订租约 → 交付房屋");
    }
    
    @Override
    public void saleHouse() {
        System.out.println("业主执行房屋出售流程:签订合同 → 办理过户");
    }
}

步骤3:实现代理类(持有真实对象引用,添加增强逻辑)

java
复制
下载
// 代理类:中介(在真实业务前后添加增强逻辑)
public class HouseProxy implements HouseSubject {
    private HouseSubject realSubject;  // 持有真实主题引用
    
    public HouseProxy(HouseSubject realSubject) {
        this.realSubject = realSubject;
    }
    
    @Override
    public void rentHouse() {
        System.out.println("[中介] 前置审核:房源真实性验证");
        realSubject.rentHouse();       // 调用真实业务
        System.out.println("[中介] 后置服务:协助办理租赁备案");
    }
    
    @Override
    public void saleHouse() {
        System.out.println("[中介] 前置服务:挂牌推广、带客看房");
        realSubject.saleHouse();
        System.out.println("[中介] 后置服务:协助办理过户手续");
    }
}

步骤4:客户端调用

java
复制
下载
public class Client {
    public static void main(String[] args) {
        HouseSubject owner = new RealHouseSubject();
        HouseProxy agent = new HouseProxy(owner);
        agent.rentHouse();  // 通过代理访问真实对象
    }
}

输出结果:

text
复制
下载
[中介] 前置审核:房源真实性验证
业主执行房屋租赁流程:签订租约 → 交付房屋
[中介] 后置服务:协助办理租赁备案

3.3 静态代理的优缺点分析

维度说明
优点实现简单、代码结构清晰、易于理解和调试-2
缺点每个被代理类都需要编写一个代理类,代码量大、维护成本高;接口新增方法时,代理类也必须同步修改-2
📌 适用场景对被代理类进行简单扩展,且被代理类数量较少时-1

💡 真实情况:在实际企业级开发中,静态代理的应用非常非常少,因为一旦需要代理的类增多,代码量会急剧膨胀-39

四、JDK 动态代理:基于接口的运行时代理

4.1 概念定义

JDK 动态代理是 Java 语言原生提供的一种动态代理机制,它在运行时动态生成代理类,无需手动编写代理类代码。其核心依赖于 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口-17

核心要点: 被代理的类必须实现至少一个接口

4.2 核心组件拆解

组件说明
Proxy提供 newProxyInstance() 静态方法,用于动态创建代理对象
InvocationHandler定义 invoke() 方法,负责处理代理对象的方法调用逻辑

4.3 代码示例

继续使用上面的房屋租赁场景,改用 JDK 动态代理实现:

步骤1:定义接口和目标类(与静态代理相同)

java
复制
下载
public interface HouseSubject { void rentHouse(); }
public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("业主执行房屋租赁流程:签订租约 → 交付房屋");
    }
}

步骤2:实现 InvocationHandler(定义增强逻辑)

java
复制
下载
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class HouseInvocationHandler implements InvocationHandler {
    private Object target;  // 持有被代理对象的引用
    
    public HouseInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
        // 前置增强
        System.out.println("[中介] 前置审核:房源真实性验证");
        
        // 调用目标对象的真实方法(通过反射)
        Object result = method.invoke(target, args);
        
        // 后置增强
        System.out.println("[中介] 后置服务:协助办理租赁备案");
        return result;
    }
}

步骤3:创建动态代理对象并调用

java
复制
下载
import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        RealHouseSubject realSubject = new RealHouseSubject();
        
        // 创建 InvocationHandler
        HouseInvocationHandler handler = new HouseInvocationHandler(realSubject);
        
        // 动态创建代理对象
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
            realSubject.getClass().getClassLoader(),  // 类加载器
            realSubject.getClass().getInterfaces(),   // 目标类实现的接口
            handler                                    // InvocationHandler
        );
        
        // 调用代理方法
        proxy.rentHouse();
    }
}

4.4 底层原理简析

JDK 动态代理的核心原理基于 Java 反射机制-11

  1. 调用 Proxy.newProxyInstance() 时,JVM 会在运行时动态生成一个新的类(通常命名为 $Proxy0);

  2. 这个新生成的代理类实现了传入的所有接口

  3. 在代理类的每个方法中,都会调用 InvocationHandlerinvoke() 方法-11

  4. invoke() 方法内部通过 反射method.invoke(target, args))调用目标对象的真实方法。

🧠 一句话记忆:JDK 动态代理 = 接口 + 反射 + Proxy + InvocationHandler

4.5 优缺点总结

维度说明
优点无需手动编写代理类,代码复用性好;灵活性强,运行时决定代理行为-3
缺点被代理类必须实现接口;基于反射调用,性能略低于直接调用-3
📌 适用场景被代理对象已实现接口,需要动态添加横切逻辑(如日志、事务)-1

五、CGLIB 动态代理:基于继承的类代理

5.1 概念定义

CGLIB(Code Generation Library) 是一个强大的、高性能的代码生成库,它通过 ASM 字节码操作框架在运行时动态生成目标类的子类来实现代理-25。与 JDK 动态代理不同,CGLIB 不要求目标类实现任何接口

5.2 核心组件拆解

组件说明
EnhancerCGLIB 的核心类,用于生成动态代理类
MethodInterceptor定义 intercept() 方法,负责拦截并增强目标方法调用

5.3 代码示例

场景: 代理一个没有实现任何接口的普通类。

步骤1:定义目标类(无需接口)

java
复制
下载
// 目标类:没有实现任何接口
public class UserService {
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
    
    public void deleteUser(int id) {
        System.out.println("删除用户ID:" + id);
    }
}

步骤2:实现 MethodInterceptor(定义增强逻辑)

java
复制
下载
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class UserMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, 
                            MethodProxy proxy) throws Throwable {
        // 前置增强:权限校验
        System.out.println("[前置] 权限校验通过,准备调用:" + method.getName());
        
        // 调用父类(目标类)的方法
        Object result = proxy.invokeSuper(obj, args);
        
        // 后置增强:日志记录
        System.out.println("[后置] 方法执行完成:" + method.getName());
        return result;
    }
}

步骤3:通过 Enhancer 创建代理对象

java
复制
下载
import net.sf.cglib.proxy.Enhancer;

public class CglibDemo {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        
        // 设置父类(目标类)
        enhancer.setSuperclass(UserService.class);
        
        // 设置回调拦截器
        enhancer.setCallback(new UserMethodInterceptor());
        
        // 创建代理对象(动态生成子类实例)
        UserService proxy = (UserService) enhancer.create();
        
        // 调用代理方法
        proxy.addUser("张三");
        proxy.deleteUser(1001);
    }
}

输出结果:

text
复制
下载
[前置] 权限校验通过,准备调用:addUser
添加用户:张三
[后置] 方法执行完成:addUser
[前置] 权限校验通过,准备调用:deleteUser
删除用户ID:1001
[后置] 方法执行完成:deleteUser

5.4 底层原理简析

CGLIB 动态代理的实现原理如下-25

  1. 创建 Enhancer 对象,设置目标类作为父类;

  2. CGLIB 使用 ASM 字节码操作框架在运行时动态生成一个继承自目标类的子类;

  3. 生成的子类会覆盖所有非 final 的方法

  4. 覆盖的方法内部会委托给 MethodInterceptor.intercept() 方法执行拦截逻辑;

  5. intercept() 中可以通过 MethodProxy.invokeSuper() 调用父类(目标类)的原始方法。

🧠 一句话记忆:CGLIB 动态代理 = 字节码生成 + 继承子类 + MethodInterceptor

5.5 ⚠️ 重要限制

CGLIB 动态代理无法代理 final 类或 final 方法,因为 Java 语言规范规定 final 类不能被继承,final 方法不能被重写-28

六、概念关系与对比总结

6.1 三种代理方式对比

对比维度静态代理JDK 动态代理CGLIB 动态代理
代理方式编译期生成代理类运行时基于接口生成运行时基于继承生成子类
接口要求需要接口必须有接口不需要接口
底层技术手动编码反射 + ProxyASM 字节码增强
代理限制无特殊限制只能代理接口方法无法代理 final 类/方法
生成开销无(编译期完成)较小较大(字节码生成)
调用性能最快(直接调用)JDK 8 后优化较好较快(直接调用子类方法)
依赖库JDK 原生JDK 原生需引入 CGLIB 库
典型应用简单场景、少量类Spring AOP(代理接口)Spring AOP(代理类)、Hibernate

6.2 性能说明

关于 JDK 动态代理与 CGLIB 的性能对比,随 JDK 版本变化有所不同:

  • JDK 6:调用次数较少时两者差距不明显,次数增加后 CGLIB 稍快;

  • JDK 7/8:调用次数较少时 JDK 动态代理比 CGLIB 快约 30%;调用次数增加后,差距进一步拉大-28

  • JDK 9+:经过持续优化,两者性能差距已显著缩小-28

📌 实践建议:在日常开发中,无需过度纠结于微小的性能差异,应根据实际需求选择——有接口用 JDK,无接口用 CGLIB。

6.3 一句话记忆

静态代理是“手写代码、编译绑定”;JDK 动态代理是“接口反射、运行时生成”;CGLIB 动态代理是“字节码继承、无接口也行”。

七、实战应用:Spring AOP 中的代理选择

Spring AOP(面向切面编程)的实现本质上依赖于代理模式-47。Spring 会根据目标对象是否实现接口,自动选择使用 JDK 动态代理还是 CGLIB 动态代理-28

  • 如果目标对象实现了至少一个接口,Spring AOP 默认使用 JDK 动态代理

  • 如果目标对象没有实现任何接口,Spring AOP 自动切换到 CGLIB 动态代理

如果需要强制使用 CGLIB 代理(例如希望代理类中的所有方法,包括非接口方法),可以在 Spring 配置中设置:

java
复制
下载
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // proxyTargetClass = true 强制使用 CGLIB 代理
}

这也是为什么 Spring AOP 能无缝适配各种场景——它把代理模式的选择逻辑封装在了框架内部,使用者只需关注业务本身。

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

面试题 1:JDK 动态代理和 CGLIB 动态代理有什么区别?

参考答案(建议背诵要点):

  1. 实现方式不同:JDK 动态代理基于接口,通过 Proxy 类和 InvocationHandler 实现;CGLIB 基于继承,通过 Enhancer 类和 MethodInterceptor 实现-28

  2. 目标类要求不同:JDK 要求目标类必须实现接口;CGLIB 不需要接口,但不能代理 final 类或 final 方法-28

  3. 底层技术不同:JDK 基于反射机制;CGLIB 基于 ASM 字节码生成技术-28

  4. 性能特点:JDK 8 之前 CGLIB 调用性能更优,JDK 8+ 后两者差距缩小-28

  5. 应用场景:Spring AOP 默认根据目标类是否实现接口自动选择,有接口用 JDK,无接口用 CGLIB-28


面试题 2:静态代理和动态代理的区别是什么?

参考答案:

对比维度静态代理动态代理
代理类创建时机编译时手动编写运行时动态生成
代码量每个目标类需要一个代理类一个代理处理器可复用
灵活性较低,接口变更需修改代理类较高,运行时动态决定行为
性能最快(无反射开销)略低(反射/字节码生成开销)
维护成本高(类多时代理类膨胀)

面试题 3:JDK 动态代理为什么必须基于接口?

参考答案:

JDK 动态代理生成的代理类(如 $Proxy0)会继承 java.lang.reflect.Proxy,而 Java 是单继承的,所以生成的代理类无法再继承其他类。只能通过实现接口的方式来定义代理行为-28。这也是 JDK 动态代理与 CGLIB 在底层设计上的本质区别。


面试题 4:CGLIB 能代理 final 方法吗?为什么?

参考答案:

不能。 因为 CGLIB 是通过生成目标类的子类来实现代理的,它会在子类中覆盖(override) 父类的非 final 方法。而 Java 语言规定:final 方法不能被重写,所以 CGLIB 无法代理 final 方法;同理,final 类也不能被 CGLIB 代理-28


面试题 5:Spring AOP 中如何强制使用 CGLIB 代理?

参考答案:

在 Spring Boot 中,可以通过配置文件或注解两种方式:

方式一:配置文件(application.yml)

yaml
复制
下载
spring:
  aop:
    proxy-target-class: true

方式二:注解配置类

java
复制
下载
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig { }

设置 proxyTargetClass = true 后,Spring AOP 将强制使用 CGLIB 代理,无论目标类是否实现接口。

九、结尾总结

核心知识点回顾

知识点核心要点
代理模式的价值解耦核心业务与横切逻辑,实现功能增强而不修改原代码
静态代理编译期确定关系,需手动编写代理类,适用于简单少量场景
JDK 动态代理运行时基于接口生成,核心是 Proxy + InvocationHandler,依赖反射
CGLIB 动态代理运行时基于继承生成子类,核心是 Enhancer + MethodInterceptor,依赖字节码增强
Spring AOP 选择策略有接口用 JDK,无接口用 CGLIB,可通过配置强制使用 CGLIB

易错点提醒

  • ⚠️ JDK 动态代理的目标类必须实现接口——这是面试中最容易被忽略的前提条件。

  • ⚠️ CGLIB 无法代理 final 类和 final 方法——设计类时需要注意。

  • ⚠️ 静态代理并非“无用”,而是“局限”——理解它的设计思想比纠结实用性更重要。


📘 下篇预告:下一篇将深入 Spring AOP 源码,剖析 JdkDynamicAopProxyCglibAopProxy 的内部实现,带你从“会用”到“懂源码”的进阶之路,敬请期待!


如果本文对你有帮助,欢迎点赞、收藏、转发,让更多需要的人看到。有任何问题或想深入探讨的话题,欢迎在评论区留言交流。

上海羊羽卓进出口贸易有限公司 备案号:沪ICP备2024077106号