夜雨聆风学习资料网

ARTICLE · 1092186

Java动态代理是如何实现的?从入门到源码深度解析

Java动态代理是如何实现的?从入门到源码深度解析

今天我们来聊一个Java中非常核心且实用的技术——动态代理。

提到动态代理,很多同学的第一反应是:“哦,Spring AOP嘛。”确实,Spring AOP是动态代理最广为人知的应用场景,但动态代理的价值远不止于此。它几乎是所有Java高级框架的底层基石:MyBatis的Mapper接口、RPC框架的远程调用、日志框架、事务管理……背后都有动态代理的影子。

那么,Java动态代理到底是如何实现的?JDK代理和CGLIB代理又有什么区别?今天这篇文章,我们就来彻底搞懂它。

一、什么是动态代理?为什么需要它?

代理模式的核心思想很简单:不直接访问目标对象,而是通过一个代理对象来间接访问,从而可以在目标方法调用前后插入额外的逻辑。

比如我们要给所有业务方法加上日志记录。如果写静态代理,每个业务类都要对应写一个代理类,代码量爆炸且难以维护。而动态代理的优势在于:代理类是在程序运行时动态生成的,我们只需要写好“要插入的逻辑”,JVM会自动帮我们生成代理类。

二、JDK动态代理:基于接口的实现

JDK动态代理是Java原生支持的方式,位于java.lang.reflect包下,核心只有两个东西:Proxy类和InvocationHandler接口。

核心API

Proxy类提供了静态方法newProxyInstance,可以直接创建代理对象:

public static Object newProxyInstance(    ClassLoader loader,    Class<?>[] interfaces,    InvocationHandler h)

参数含义:

  • loader:目标类的类加载器

  • interfaces:目标类实现的接口数组

  • h:调用处理器,所有方法调用都会被转发到这里

InvocationHandler:逻辑编织器

InvocationHandler接口只有一个方法:

public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable;

这三个参数的含义:

  • proxy:生成的代理对象本身(注意,不是目标对象)

  • method:当前被调用的方法(通过反射获取)

  • args:方法调用的参数数组

当调用代理对象的任何接口方法时,JVM不会直接执行方法,而是将这个调用编码并转发到invoke方法中。我们就在这个方法里“编织”横切逻辑和目标方法调用。

一个完整的例子

假设我们有一个UserService接口和实现类,想给它加上方法执行耗时的监控:

public interface UserService {    void saveUser(String name);}public class UserServiceImpl implements UserService {    public void saveUser(String name) {        System.out.println("保存用户: " + name);    }}

实现InvocationHandler:

public class PerformanceHandler implements InvocationHandler {    private Object target; // 目标对象    public PerformanceHandler(Object target) {        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        long start = System.currentTimeMillis();        // 通过反射调用目标方法        Object result = method.invoke(target, args);        long end = System.currentTimeMillis();        System.out.println(method.getName() + " 耗时: " + (end - start) + "ms");        return result;    }}

使用动态代理:

UserService target = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(    target.getClass().getClassLoader(),    target.getClass().getInterfaces(),    new PerformanceHandler(target));proxy.saveUser("张三"); // 会先打印耗时,再执行真正的保存逻辑

背后的实现原理

JDK动态代理的代理类长什么样?它的类名通常是$Proxy0这样的格式,并且有以下关键特征:

  1. 继承java.lang.reflect.Proxy:这是强制要求,因为Proxy类负责持有InvocationHandler引用

  2. 实现指定的所有接口:所以JDK代理只能基于接口,不能代理没有接口的类

  3. 每个方法内部调用super.h.invoke(this, method, args):这里的h就是传入的InvocationHandler

代理类的生成过程大致是:JVM在运行时通过ProxyGenerator.generateProxyClass方法,用字节码技术为指定接口动态生成一个实现了所有接口方法的类,然后加载到JVM中。

三、CGLIB动态代理:基于继承的实现

JDK代理有一个硬伤:目标类必须实现接口。如果我们要代理的是一个普通类,没有接口怎么办?这时候就需要CGLIB出场了。

核心原理

CGLIB(Code Generation Library)是一个强大的字节码生成库,底层使用ASM框架。它的核心思路是:在运行时动态生成目标类的一个子类,通过重写父类的方法来实现代理。

因为是基于继承,所以CGLIB有两个限制:

  • 不能代理final类(无法继承)

  • 不能代理final方法(无法重写)

核心API

CGLIB使用MethodInterceptor接口,类似于JDK的InvocationHandler:

public interface MethodInterceptor {    Object intercept(Object obj, Method method, Object[] args, MethodProxyproxy) throwsThrowable;}

关键参数:

  • obj:代理子类对象(注意,是子类实例,不是原始目标对象)

  • proxy:用于调用父类(目标类)方法的MethodProxy

调用目标方法时使用proxy.invokeSuper(obj, args),这会触发对父类原始方法的调用。

使用示例

Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserServiceImpl.class); // 设置父类enhancer.setCallback(new MethodInterceptor() {    @Override    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {        long start = System.currentTimeMillis();        Object result = proxy.invokeSuper(obj, args); // 调用父类方法        System.out.println(method.getName() + " 耗时: " + (System.currentTimeMillis() - start) + "ms");        return result;    }});UserServiceImpl proxy = (UserServiceImpl) enhancer.create();proxy.saveUser("李四");

四、JDK vs CGLIB:到底选哪个?

这是面试中的高频问题,我们来做一个清晰的对比:

对比维度
JDK动态代理
CGLIB动态代理
代理方式
基于接口
基于继承(生成子类)
目标类要求
必须实现接口
不能是final类
方法限制
只能代理接口方法
不能代理final方法
底层技术
反射 + Proxy
ASM字节码增强
生成速度
较快
较慢(需生成多个类)
调用速度
反射调用,较慢
方法索引调用,较快

五、Spring AOP的选择策略

Spring AOP是动态代理最大的应用场景,它的选择策略很明确:

  1. 如果目标Bean有接口:默认使用JDK动态代理

  2. 如果目标Bean没有接口:使用CGLIB动态代理

  3. 可以通过配置强制使用CGLIB:proxy-target-class=true

六、动态代理的应用场景

动态代理的价值远不止AOP,它的典型应用包括:

  1. Spring AOP:事务、日志、权限、缓存等横切关注点的统一处理

  2. MyBatis Mapper接口:你写的Mapper接口没有实现类,MyBatis用动态代理帮你生成实现

  3. RPC远程调用:客户端持有的“服务接口”其实是代理,调用时封装网络请求

  4. 测试Mock:Mockito等框架用动态代理创建Mock对象

  5. 数据库连接池:对Connection的close方法进行拦截,实现连接归还

写在最后

动态代理的精髓在于:将“做什么”(目标逻辑)和“额外做什么”(横切逻辑)解耦,让框架在运行时动态地将它们编织在一起。

JDK动态代理依赖接口,CGLIB依赖继承,两者各有适用场景。理解它们的实现原理,不仅能帮你应对面试,更能让你在阅读Spring、MyBatis等框架源码时,看透那些“魔法”背后的本质。

相关学习资料