ARTICLE · 1092186
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;}@Overridepublic 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这样的格式,并且有以下关键特征:
继承
java.lang.reflect.Proxy:这是强制要求,因为Proxy类负责持有InvocationHandler引用实现指定的所有接口:所以JDK代理只能基于接口,不能代理没有接口的类
每个方法内部调用
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() {@Overridepublic 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:到底选哪个?
这是面试中的高频问题,我们来做一个清晰的对比:
五、Spring AOP的选择策略
Spring AOP是动态代理最大的应用场景,它的选择策略很明确:
如果目标Bean有接口:默认使用JDK动态代理
如果目标Bean没有接口:使用CGLIB动态代理
可以通过配置强制使用CGLIB:
proxy-target-class=true
六、动态代理的应用场景
动态代理的价值远不止AOP,它的典型应用包括:
Spring AOP:事务、日志、权限、缓存等横切关注点的统一处理
MyBatis Mapper接口:你写的Mapper接口没有实现类,MyBatis用动态代理帮你生成实现
RPC远程调用:客户端持有的“服务接口”其实是代理,调用时封装网络请求
测试Mock:Mockito等框架用动态代理创建Mock对象
数据库连接池:对Connection的close方法进行拦截,实现连接归还
写在最后
动态代理的精髓在于:将“做什么”(目标逻辑)和“额外做什么”(横切逻辑)解耦,让框架在运行时动态地将它们编织在一起。
JDK动态代理依赖接口,CGLIB依赖继承,两者各有适用场景。理解它们的实现原理,不仅能帮你应对面试,更能让你在阅读Spring、MyBatis等框架源码时,看透那些“魔法”背后的本质。