目录
- @[TOC](目录)
- 前言
- 1. 比喻理解
- 2. 代码理解
- 1. 签名
- 2. 参数1:ClassLoader loader (类加载器)
- 3. 参数2:`Class<?>[] interfaces` (接口数组)
- 4. 参数3:InvocationHandler h (调用处理器)
- 3. 测试与运行
- 4. 代理字节码原理
- 总结
目录
- @[TOC](目录)
- 前言
- 1. 比喻理解
- 2. 代码理解
- 1. 签名
- 2. 参数1:ClassLoader loader (类加载器)
- 3. 参数2:`Class<?>[] interfaces` (接口数组)
- 4. 参数3:InvocationHandler h (调用处理器)
- 3. 测试与运行
- 4. 代理字节码原理
- 总结
前言
通过一个通俗易懂的比喻,快速理解 Java 动态代理的核心设计思想。
通过手写 JDK 动态代理的完整代码,详细剖析其核心参数与运行机制,并深入 JVM 内存中的代理类字节码,揭示动态代理的底层实现原理。
1. 比喻理解
例子:
- 大明星(小美):十分擅长唱歌和跳舞。打算举办演唱会,举办演唱会的过程非常繁琐。小美只想把核心精力放在唱歌和跳舞上。
- 代理(小帅):专门负责处理小美唱歌和跳舞之外的所有繁杂事务。
- 赞助商:希望小美来唱歌时,不能直接和小美对话,必须先和经纪人小帅交涉。
- 调用过程:小帅和赞助商谈妥后(前置准备),把小美带到舞台上,小美只管唱歌(核心业务)。唱完歌后,小帅还要负责结算收尾工作(后置收尾)。
在这个过程中,小帅就是小美的代理人,也就是我们代码中常说的代理对象。
2. 代码理解
我们首先写一个接口,叫做Star(明星),声明两个方法:sing和dance。
publicinterfaceStar{Stringsing(StringsongName);voiddance();}小美是大明星,实现Star接口,擅长sing与dance:
publicclassBigStarimplementsStar{@OverridepublicStringsing(StringsongName){System.out.println("小美正在唱"+songName);return"谢谢大家";}@Overridepublicvoiddance(){System.out.println("小美正在跳舞");}}接下来,我们编写用于创建代理对象的工具类ProxyUtil。
importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassProxyUtil{publicstaticStarcreateProxy(Starstar){return(Star)Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),//参数1newClass<?>[]{Star.class},//参数2newInvocationHandler(){//参数3@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{if("sing".equals(method.getName())||"dance".equals(method.getName())){System.out.println("找演唱会地点");Objectresult=method.invoke(star,args);System.out.println("收尾");returnresult;}returnmethod.invoke(star,args);}});}}为了让代理对象正常工作,Proxy.newProxyInstance必须明确以下三个参数:
1. 签名
publicstaticObjectnewProxyInstance(ClassLoaderloader,Class<?>[]interfaces,InvocationHandlerh)2. 参数1:ClassLoader loader (类加载器)
在程序运行过程中,动态代理会在内存中实时生成代理类的字节码。为了让 JVM 识别并加载该字节码,需要提供一个类加载器。通常使用当前工具类的类加载器ProxyUtil.class.getClassLoader()或被代理类的类加载器。
3. 参数2:Class<?>[] interfaces(接口数组)
此参数表示代理类要实现哪些接口。
在我们的例子中,代理要代替明星唱歌跳舞,因此需要实现Star接口,写为new Class<?>[]{Star.class}。
4. 参数3:InvocationHandler h (调用处理器)
当外部调用代理对象的任何方法时,都会转发到此处理器的invoke方法中执行。
3. 测试与运行
我们编写Main类进行测试:
publicclassMain{publicstaticvoidmain(String[]args){// 1. 创建真实对象BigStarbigStar=newBigStar();// 2. 创建代理对象StarproxyStar=ProxyUtil.createProxy(bigStar);// 3. 调用代理方法Stringret=proxyStar.sing("Lucky");System.out.println(ret);}}运行后,控制台输出如下:
找演唱会地点 小美正在唱Lucky 收尾 谢谢大家4. 代理字节码原理
看看代理对象的伪代码:
importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;importjava.lang.reflect.UndeclaredThrowableException;publicfinalclass$Proxy0extendsProxyimplementsStar{privatestaticMethodm1;// 代表 Object.equals()privatestaticMethodm2;// 代表 Object.toString()privatestaticMethodm3;// 代表 Star.sing()privatestaticMethodm4;// 代表 Star.dance()static{try{m1=Class.forName("java.lang.Object").getMethod("equals",Object.class);m2=Class.forName("java.lang.Object").getMethod("toString");m3=Class.forName("Star").getMethod("sing",String.class);m4=Class.forName("Star").getMethod("dance");}catch(Exceptione){thrownewNoSuchMethodError(e.getMessage());}}public$Proxy0(InvocationHandlerh){super(h);// 将处理器传递给父类 java.lang.reflect.Proxy}@OverridepublicfinalStringsing(StringsongName){try{// 所有方法调用都会转发到超类持有的 InvocationHandler 的 invoke 方法中return(String)super.h.invoke(this,m3,newObject[]{songName});}catch(Error|RuntimeExceptione){throwe;}catch(Throwablet){thrownewUndeclaredThrowableException(t);}}@Overridepublicfinalvoiddance(){try{super.h.invoke(this,m4,null);}catch(Error|RuntimeExceptione){throwe;}catch(Throwablet){thrownewUndeclaredThrowableException(t);}}}动态生成的代理类命名为$Proxy0,它继承了java.lang.reflect.Proxy并且实现了目标接口Star。Java 不支持多重继承,$Proxy0已经占用了继承Proxy的名额。所以只能代理接口,无法代理实例化的类.
$Proxy0的构造方法接收一个InvocationHandler h,并通过super(h)传给父类Proxy统一保管。
在重写的接口方法(如sing、dance)中,没有包含任何业务逻辑,而是直接调用super.h.invoke(this, method, args).
总结
- 动态代理的核心思想是解耦,通过代理对象将繁琐的非核心业务与真实对象的核心业务隔离开来。
- JDK 动态代理利用
java.lang.reflect.Proxy类的newProxyInstance方法,在运行期动态地在内存中生成实现了指定接口的代理类字节码。 - 代理类(如
$Proxy0)继承自Proxy并实现目标接口,其内部持有一个InvocationHandler对象,所有接口方法的调用都会被重定向并转发到该处理器的invoke方法中执行。