答题AI助手|2026年4月8日速览:代理模式静态JDK与CGLIB全对比

小编头像

小编

管理员

发布于:2026年05月11日

8 阅读 · 0 评论

2026年4月8日 14:30 | 技术科普 + 原理讲解 + 代码示例 + 面试要点

代理模式是Java开发中最常用也最易混淆的核心设计模式之一。借助答题AI助手的检索能力,本文将系统梳理代理模式三种实现方式:静态代理、JDK动态代理与CGLIB动态代理,帮你彻底理清概念,解决“只会用、不懂原理、面试答不出”的学习痛点。


一、为什么需要代理模式?

假设业务中有日志记录、权限校验、事务管理等需求,最直接的方式是每个方法里都塞一段相同的代码:

java
复制
下载
public void doSomething() {
    System.out.println("开始日志...");
    // 核心业务逻辑
    System.out.println("结束日志...");
}

这种方法带来的问题

  1. 代码冗余——同样的日志代码在多个方法中重复出现;

  2. 耦合度高——业务逻辑与非业务逻辑混在一起,修改日志规则就要改动业务代码;

  3. 扩展性差——如果要增加缓存或监控功能,需要修改所有相关方法;

  4. 维护困难——接口增加新方法时,所有实现类都得同步修改。

代理模式正是为解决这些问题而生的:在不修改原始类代码的前提下,通过代理对象控制对目标对象的访问,并在方法调用前后插入额外的增强逻辑。


二、核心概念A:静态代理

静态代理(Static Proxy) :在程序编译期就已经确定代理类和目标类的关系,代理类需要手动编写,且与目标类实现相同的接口-1

生活化类比:周杰伦要开演唱会。他本人(目标对象)只管唱歌。经纪公司(代理对象)负责面谈、签合同、订机票、收钱等杂事。粉丝(客户端)通过经纪公司接触周杰伦,不需要直接和他本人打交道-30

代码示例

java
复制
下载
// 1. 抽象接口
public interface Star {
    void sing();
}

// 2. 真实角色——周杰伦本人
public class RealStar implements Star {
    @Override
    public void sing() {
        System.out.println("周杰伦唱《七里香》~");
    }
}

// 3. 代理角色——经纪人
public class ProxyStar implements Star {
    private Star star;  // 持有真实角色的引用
    
    public ProxyStar(Star star) {
        this.star = star;
    }
    
    @Override
    public void sing() {
        System.out.println("经纪人:面谈、签合同、订机票...");
        star.sing();  // 调用真实角色
        System.out.println("经纪人:收钱");
    }
}

// 4. 客户端使用
public class Client {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxy = new ProxyStar(realStar);
        proxy.sing();  // 通过代理访问
    }
}

静态代理的核心问题:如果目标对象较多,就需要为每个目标对象写一个代理类,代码量成倍增长,维护成本极高-。这也正是动态代理出现的原因。


三、核心概念B:动态代理

动态代理(Dynamic Proxy) :在程序运行期间动态创建代理类,无需手动编写代理类代码。JVM在运行时根据指定的接口或目标类,动态生成代理对象的字节码并加载到内存中-37

3.1 JDK动态代理

定义:JDK动态代理是Java原生提供的动态代理机制,位于java.lang.reflect包中,依赖Proxy类和InvocationHandler接口。要求目标类必须实现至少一个接口,因为生成的代理类会实现这些接口-60

代码示例

java
复制
下载
// 1. 定义接口(JDK代理强制要求)
public interface UserService {
    void addUser(String name);
}

// 2. 目标类实现接口
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 3. 实现InvocationHandler接口
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;
    }
}

// 4. 创建代理对象并使用
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        InvocationHandler handler = new LogInvocationHandler(target);
        
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
        
        proxy.addUser("张三");
    }
}

执行结果

text
复制
下载
【日志】addUser方法开始执行
添加用户:张三
【日志】addUser方法执行结束

3.2 CGLIB动态代理

定义:CGLIB(Code Generation Library)是一个高性能的代码生成库,通过字节码技术动态生成目标类的子类来实现代理。不要求目标类实现接口,因此适用范围更广-22

代码示例

java
复制
下载
// 1. 目标类——不需要实现任何接口
public class UserService {
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 2. 实现MethodInterceptor接口
public class LogMethodInterceptor 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. 创建代理对象
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LogMethodInterceptor());
        
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("张三");
    }
}

四、概念关系与区别总结

对比维度静态代理JDK动态代理CGLIB动态代理
生成时机编译期运行期运行期
是否需要接口需要需要(强制)不需要
实现原理手动编码反射 + 字节码生成ASM字节码生成子类
能否代理final类/方法可代理不涉及不能
代码量每个类一个代理类一个Handler复用一个Interceptor复用
性能最高中等(反射开销)较高(直接调用)
适用场景少量固定代理接口代理、Spring AOP无接口代理、Spring AOP

一句话记忆静态代理是“手写的替身”,JDK动态代理是“反射生成的接口替身”,CGLIB是“字节码生成的子类替身”。


五、底层原理 / 技术支撑

JDK动态代理的底层支撑——反射 + 字节码生成

当调用Proxy.newProxyInstance()时,JVM执行三个步骤:先在内存中根据传入的接口拼装出代理类的字节码(格式固定),该代理类实现所有指定接口,每个方法的实现都调用InvocationHandler.invoke();然后将字节码加载进JVM生成Class<?>对象;最后通过反射调用代理类的构造函数生成实例-9

CGLIB动态代理的底层支撑——ASM字节码操作框架

CGLIB底层使用ASM(一个轻量级的字节码处理框架)来操作字节码。首先创建Enhancer对象并设置目标类为父类;然后设置MethodInterceptor回调;调用create()方法时,CGLIB使用ASM动态生成目标类的子类,该子类覆盖所有非final方法,将调用委托给拦截器-22


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

1. 什么是代理模式?它的核心作用是什么?

参考答案:代理模式是一种结构型设计模式,通过创建一个代理对象来控制对目标对象的访问。核心作用有两个:控制访问(在不直接访问目标对象的情况下间接操作)和增强功能(在目标方法执行前后添加额外逻辑,如日志、事务、权限校验等)-37

2. 静态代理和动态代理有什么区别?

参考答案时机不同——静态代理在编译期确定代理关系,代理类在代码中已写好;动态代理在运行期动态生成代理类。代码量不同——静态代理需为每个目标类写一个代理类,代码冗余;动态代理只需编写一次调用处理器或拦截器,即可为多个目标类生成代理对象。灵活性不同——接口新增方法时,静态代理的代理类和目标类都需修改,违背开闭原则;动态代理无需修改--1

3. 为什么JDK动态代理只能代理接口?

参考答案:因为JDK动态代理生成的代理类已经继承了Proxy。Java是单继承语言,一个类只能继承一个父类,无法再继承目标类。所以JDK代理只能通过实现接口的方式来定义代理对象的行为。生成的代理类会实现目标类所实现的接口,并在接口方法中调用InvocationHandler.invoke()-60

4. JDK动态代理和CGLIB动态代理各自有什么优缺点?

参考答案JDK动态代理——优点是不依赖第三方库,由JDK原生支持;缺点是要求目标类必须实现接口,无法代理无接口的类。CGLIB动态代理——优点是不需要目标类实现接口,可以代理任意普通类;缺点是依赖CGLIB库,无法代理final类或final方法(因为基于继承生成子类),且生成代理类的过程比JDK稍复杂-22-54

5. Spring AOP默认使用哪种代理方式?什么情况下会切换?

参考答案:Spring AOP默认优先使用JDK动态代理。如果目标类实现了至少一个接口,Spring默认使用JDK代理;如果目标类没有实现任何接口,Spring会自动切换为CGLIB代理。也可以通过配置强制使用CGLIB代理-22


七、结尾总结

回顾全文核心知识点:

类型核心特点一句话记忆
静态代理编译期生成,手动编码手写的替身,每个目标一个代理类
JDK动态代理运行期生成,需要接口,基于反射反射生成的接口替身
CGLIB动态代理运行期生成,无需接口,基于字节码ASM生成的子类替身

重点强调:实际开发中动态代理使用频率远超静态代理,但静态代理是理解代理模式的敲门砖,不可跳过。面试中关于JDK代理为什么只能代理接口的问题,核心原因是“代理类已继承Proxy类,无法再继承目标类”。

下期预告:Spring AOP底层源码解析——看JDK与CGLIB如何被Spring整合运用,并附源码级流程图。

标签:

相关阅读