2026年4月9日 一文吃透Java动态代理:AI助手小猫带你搞懂JDK vs CGLIB与Spring AOP底层原理

小编 1 0

在Java面试中,“动态代理”几乎是个必考的知识点。很多人会用Spring AOP,却说不清它底层到底是怎么“动态”起来的。JDK动态代理和CGLIB有什么区别?为什么Spring默认优先用JDK?本文AI助手小猫将从零开始,带你彻底搞懂动态代理的核心原理、手写代码示例和高频面试题。

一、痛点切入:为什么需要动态代理?

先来看一个常见场景:你有一个UserService,需要在每个方法执行前后记录日志、统计耗时。

java
复制
下载
public class UserServiceImpl implements UserService {

@Override public void createUser(String name) { System.out.println("创建用户:" + name); } @Override public void deleteUser(Long id) { System.out.println("删除用户:" + id); } }

如果你在每个方法里都手动加上日志代码,很快就会发现——代码重复、耦合度高、维护困难。更糟糕的是,如果项目中有几十上百个Service,每个都要这样改一遍。

静态代理:一个笨办法

静态代理的思路是:为每个目标类单独写一个代理类,实现相同接口,在代理类中嵌入增强逻辑。

java
复制
下载
public class UserServiceProxy implements UserService {
    private UserService target;
    public UserServiceProxy(UserService target) { this.target = target; }
    @Override
    public void createUser(String name) {
        System.out.println("开始执行 createUser");
        target.createUser(name);
        System.out.println("执行结束");
    }
}

静态代理的三大硬伤:①每个接口都要单独写一个代理类,代码量翻倍;②接口新增方法时,代理类必须同步修改;③当需要批量代理几十个Service时,几乎不可行-8

动态代理:解决方案

动态代理的魔力在于:在程序运行时,由JVM动态生成代理类和代理对象,无需手动编写代理代码。Java中主要有两种实现方式:JDK动态代理和CGLIB动态代理-8

二、核心概念:JDK动态代理

什么是JDK动态代理?

JDK动态代理(JDK Dynamic Proxy) 是Java标准库提供的一种基于接口的动态代理机制。它允许在运行期动态创建某个接口的实例,并将方法调用“代理”给InvocationHandler来处理-15

生活化类比:你找商务谈需求,商务就是你的“代理”。你只需要说“我要什么功能”,商务负责去协调研发团队。商务和研发之间有一套标准流程,就像JDK动态代理中InvocationHandler负责统一处理所有方法调用。

三大核心组件

JDK动态代理由以下三个核心组件构成-8

  • InvocationHandler(调用处理器) :你需要实现这个接口,在invoke()方法中编写方法调用前后的增强逻辑。

  • Method(方法对象) :代表被调用的方法,通过它可以利用反射机制调用目标方法。

  • Proxy(代理类) :JDK提供的工具类,核心方法是Proxy.newProxyInstance(),用于动态生成代理类并创建代理实例。

极简代码示例

java
复制
下载
// 1. 定义接口
public interface UserService {
    void createUser(String name);
}

// 2. 实现目标类
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String name) {
        System.out.println("创建用户:" + name);
    }
}

// 3. 自定义InvocationHandler
public class LogHandler implements InvocationHandler {
    private Object target;
    public LogHandler(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("【后置】执行结束");
        return result;
    }
}

// 4. 使用Proxy创建代理对象
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogHandler(target)
);
proxy.createUser("张三");
// 输出:
// 【前置】开始执行:createUser
// 创建用户:张三
// 【后置】执行结束

执行流程

当调用proxy.createUser("张三")时,底层发生的是:方法调用被拦截并转发到LogHandler.invoke()方法,由它在内部通过反射调用目标对象的原始方法-1

三、关联概念:CGLIB动态代理

什么是CGLIB?

CGLIB(Code Generation Library) 是一个基于ASM字节码操作框架的代码生成库。它通过动态生成目标类的子类来实现代理,不要求目标类实现任何接口-20-30

一句话概括:JDK动态代理 = 给“有接口的类”做代理;CGLIB动态代理 = 给“没接口的普通类”也能做代理。

两大核心组件

  • MethodInterceptor:自定义的拦截器接口,实现intercept()方法定义增强逻辑-20

  • Enhancer:增强器类,用于配置目标类、设置回调、生成代理子类-20

极简代码示例

java
复制
下载
// 1. 目标类(无需实现接口)
public class UserService {
    public void createUser(String name) {
        System.out.println("创建用户:" + name);
    }
}

// 2. 自定义MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class LogInterceptor 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("【后置】执行结束");
        return result;
    }
}

// 3. 使用Enhancer生成代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LogInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.createUser("李四");
// 输出:
// 【前置】开始执行:createUser
// 创建用户:李四
// 【后置】执行结束

特别注意:CGLIB无法代理final类或final方法,因为Java语言规范禁止继承final类或重写final方法-20

四、概念关系与区别总结

对比维度JDK动态代理CGLIB动态代理
实现原理基于接口,通过反射动态生成代理类,代理类实现目标接口基于继承,通过ASM生成目标类的子类作为代理类
目标类要求必须实现至少一个接口无需接口,但类和方法不能是final
底层技术反射 + ProxyASM字节码增强
性能特点JDK 8+反射优化后,调用速度提升明显,差距缩小生成代理类较慢,但调用执行效率较高
依赖Java标准库(无需额外依赖)需引入CGLIB库(Spring已内置)
典型应用Spring AOP对接口代理Spring AOP对无接口类代理、Hibernate懒加载

-29-46

一句话记忆口诀:JDK看接口,反射造代理;CGLIB看继承,字节码生子类。

五、底层原理:它是怎么“动态”起来的?

JDK动态代理的底层原理

JDK动态代理本质上是 “动态生成字节码 + 反射机制” 的结合-Proxy.newProxyInstance()方法内部经历了三个步骤-11

  1. 拼装生成字节码:根据传入的接口列表,在内存中拼装出一个实现了这些接口的Java类字节码。这个类会继承Proxy类,并在每个接口方法的实现中调用InvocationHandler.invoke()

  2. 类加载:将内存中生成的字节码加载进JVM,生成代理类的Class对象。

  3. 通过反射创建实例:调用代理类的构造函数(接收InvocationHandler作为参数),生成代理实例。

如果你打印代理类的全类名,会看到类似jdk.proxy1.$Proxy0这样的名字,这就是JDK运行时生成的代理类-11

CGLIB动态代理的底层原理

CGLIB底层依赖ASM字节码操作框架,在运行时直接操纵字节码-25

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

  2. 设置Callback(通常是MethodInterceptor)。

  3. 调用create()方法,CGLIB使用ASM框架动态生成一个继承自目标类的子类,覆盖所有非final方法。

  4. 在生成的子类中,被覆盖的方法内部会调用MethodInterceptor.intercept(),从而实现方法拦截和增强。

两者底层逻辑的本质差异:JDK动态代理生成的代理类实现的是接口,而CGLIB生成的代理类继承的是目标类。前者通过反射调用,后者通过字节码直接调用。

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

1. JDK动态代理和CGLIB动态代理有什么区别?(⭐星标)

【答题层次】原理 → 要求 → 性能 → 场景 → 一句话总结

  • 原理差异:JDK基于接口,利用反射动态生成代理类;CGLIB基于继承,利用ASM字节码框架生成目标类的子类。

  • 目标类要求:JDK要求目标类必须实现接口;CGLIB无需接口,但目标类和方法不能是final

  • 性能差异:JDK 1.8及以上版本反射已优化,两者性能差距很小;CGLIB生成代理类较慢但调用执行快。

  • 使用场景:JDK是Java原生,无额外依赖;CGLIB需引入库,Spring AOP会根据目标类是否有接口自动选择。

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

因为JDK动态代理生成的代理类会继承java.lang.reflect.Proxy类,而Java不支持多继承,所以代理类只能通过实现接口来扩展功能。代理类的类型由传入的接口列表决定,因此只有实现了接口的类才能被代理-49

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

维度静态代理动态代理
创建时机编译期手动编写代理类运行期动态生成代理类
灵活性一对一绑定,接口变更需同步修改可通用适配多个目标类
代码量每个接口都需要单独写代理类一套横切逻辑即可批量代理

-56

4. Spring AOP底层用的是哪种代理?

Spring AOP默认使用策略:如果目标类实现了接口,则使用JDK动态代理;如果目标类没有实现任何接口,则自动切换为CGLIB动态代理。开发者也可以通过配置proxyTargetClass=true强制使用CGLIB-46

5. 动态代理在实际框架中有哪些应用场景?

  • Spring AOP:声明式事务管理、统一日志记录、权限校验拦截-56

  • MyBatis:Mapper接口的动态代理实现,将接口方法调用转换为SQL执行-

  • RPC框架:将远程服务调用伪装成本地方法调用,屏蔽网络通信细节-

七、结尾总结

回顾全文核心要点:

  • 静态代理 vs 动态代理:静态代理需要为每个接口手动编写代理类,维护成本高;动态代理在运行期动态生成,实现“一次编写,处处生效”。

  • JDK动态代理:基于接口,依赖InvocationHandlerProxy,利用反射机制,是Java原生实现。

  • CGLIB动态代理:基于继承,依赖MethodInterceptorEnhancer,利用ASM字节码框架,可代理无接口类,但无法代理final类和final方法。

  • 底层原理核心:JDK是“反射 + 动态字节码生成”,CGLIB是“ASM字节码增强 + 子类继承”。

  • 高频考点:两种代理的区别、Spring AOP的代理策略、应用场景,是面试中的必考内容。

重点关注:面试中不仅要会回答区别,更要理解“动态”二字的本质——运行期生成,编译期不写死。这是AOP等框架得以实现的核心思想。

本文是AI助手小猫【Java进阶系列】的第一篇。下一篇我们将深入讲解反射机制的底层实现与性能优化,敬请关注!