Java JDK动态代理的基本原理详细介绍
jdk动态代理详解
本文主要介绍jdk动态代理的基本原理,让大家更深刻的理解jdk proxy,知其然知其所以然。明白jdk动态代理真正的原理及其生成的过程,我们以后写jdk proxy可以不用去查demo,就可以徒手写个完美的proxy。下面首先来个简单的demo,后续的分析过程都依赖这个demo去介绍,例子采用jdk1.8运行。
jdk proxy helloworld
package com.yao.proxy; /** * created by robin */ public interface helloworld { void sayhello(); }
package com.yao.proxy; import com.yao.helloworld; /** * created by robin */ public class helloworldimpl implements helloworld { public void sayhello() { system.out.print("hello world"); } }
package com.yao.proxy; import java.lang.reflect.invocationhandler; import java.lang.reflect.method; /** * created by robin */ public class myinvocationhandler implements invocationhandler{ private object target; public myinvocationhandler(object target) { this.target=target; } public object invoke(object proxy, method method, object[] args) throws throwable { system.out.println("method :"+ method.getname()+" is invoked!"); return method.invoke(target,args); } }
package com.yao.proxy; import com.yao.helloworld; import java.lang.reflect.constructor; import java.lang.reflect.invocationhandler; import java.lang.reflect.invocationtargetexception; import java.lang.reflect.proxy; /** * created by robin */ public class jdkproxytest { public static void main(string[]args) throws nosuchmethodexception, illegalaccessexception, invocationtargetexception, instantiationexception { //这里有两种写法,我们采用略微复杂的一种写法,这样更有助于大家理解。 class<?> proxyclass= proxy.getproxyclass(jdkproxytest.class.getclassloader(),helloworld.class); final constructor<?> cons = proxyclass.getconstructor(invocationhandler.class); final invocationhandler ih = new myinvocationhandler(new helloworldimpl()); helloworld helloworld= (helloworld)cons.newinstance(ih); helloworld.sayhello(); //下面是更简单的一种写法,本质上和上面是一样的 /* helloworld helloworld=(helloworld)proxy. newproxyinstance(jdkproxytest.class.getclassloader(), new class<?>[]{helloworld.class}, new myinvocationhandler(new helloworldimpl())); helloworld.sayhello(); */ } }
运行上面的代码,这样一个简单的jdk proxy就实现了。
代理生成过程
我们之所以天天叫jdk动态代理,是因为这个代理class是由jdk在运行时动态帮我们生成。在解释代理生成过程前,我们先把-dsun.misc.proxygenerator.savegeneratedfiles=true 这个参数加入到jvm 启动参数中,它的作用是帮我们把jdk动态生成的proxy class 的字节码保存到硬盘中,帮助我们查看具体生成proxy的内容。我用的intellij idea ,代理class生成后直接放在项目的根目录下的,以具体的包名为目录结构。
代理类生成的过程主要包括两部分:
- 代理类字节码生成
- 把字节码通过传入的类加载器加载到虚拟机中
proxy类的getproxyclass方法入口:需要传入类加载器和interface
然后调用getproxyclass0方法,里面的注解解释很清楚,如果实现当前接口的代理类存在,直接从缓存中返回,如果不存在,则通过proxyclassfactory来创建。这里可以明显看到有对interface接口数量的限制,不能超过65535。其中proxyclasscache具体初始化信息如下:
proxyclasscache = new weakcache<>(new keyfactory(), new proxyclassfactory());
其中创建代理类的具体逻辑是通过proxyclassfactory的apply方法来创建的。
proxyclassfactory里的逻辑包括了包名的创建逻辑,调用proxygenerator. generateproxyclass生成代理类,把代理类字节码加载到jvm。
1.包名生成逻辑默认是com.sun.proxy,如果被代理类是 non-public proxy interface ,则用和被代理类接口一样的包名,类名默认是$proxy 加上一个自增的整数值。
2.包名类名准备好后,就是通过proxygenerator. generateproxyclass根据具体传入的接口创建代理字节码,-dsun.misc.proxygenerator.savegeneratedfiles=true 这个参数就是在该方法起到作用,如果为true则保存字节码到磁盘。代理类中,所有的代理方法逻辑都一样都是调用invocationhander的invoke方法,这个我们可以看后面具体代理反编译结果。
3.把字节码通过传入的类加载器加载到jvm中: defineclass0(loader, proxyname,proxyclassfile, 0, proxyclassfile.length);。
private static final class proxyclassfactory implements bifunction<classloader, class<?>[], class<?>> { // prefix for all proxy class names private static final string proxyclassnameprefix = "$proxy"; // next number to use for generation of unique proxy class names private static final atomiclong nextuniquenumber = new atomiclong(); @override public class<?> apply(classloader loader, class<?>[] interfaces) { map<class<?>, boolean> interfaceset = new identityhashmap<>(interfaces.length); for (class<?> intf : interfaces) { /* * verify that the class loader resolves the name of this * interface to the same class object. */ class<?> interfaceclass = null; try { interfaceclass = class.forname(intf.getname(), false, loader); } catch (classnotfoundexception e) { } if (interfaceclass != intf) { throw new illegalargumentexception( intf + " is not visible from class loader"); } /* * verify that the class object actually represents an * interface. */ if (!interfaceclass.isinterface()) { throw new illegalargumentexception( interfaceclass.getname() + " is not an interface"); } /* * verify that this interface is not a duplicate. */ if (interfaceset.put(interfaceclass, boolean.true) != null) { throw new illegalargumentexception( "repeated interface: " + interfaceclass.getname()); } } string proxypkg = null; // package to define proxy class in int accessflags = modifier.public | modifier.final; /* * record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. verify that * all non-public proxy interfaces are in the same package. */ //生成包名和类名逻辑 for (class<?> intf : interfaces) { int flags = intf.getmodifiers(); if (!modifier.ispublic(flags)) { accessflags = modifier.final; string name = intf.getname(); int n = name.lastindexof('.'); string pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxypkg == null) { proxypkg = pkg; } else if (!pkg.equals(proxypkg)) { throw new illegalargumentexception( "non-public interfaces from different packages"); } } } if (proxypkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxypkg = reflectutil.proxy_package + "."; } /* * choose a name for the proxy class to generate. */ long num = nextuniquenumber.getandincrement(); string proxyname = proxypkg + proxyclassnameprefix + num; /* * generate the specified proxy class. 生成代理类的字节码 * -dsun.misc.proxygenerator.savegeneratedfiles=true 在该部起作用 */ byte[] proxyclassfile = proxygenerator.generateproxyclass( proxyname, interfaces, accessflags); try { //加载到jvm中 return defineclass0(loader, proxyname, proxyclassfile, 0, proxyclassfile.length); } catch (classformaterror e) { /* * a classformaterror here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new illegalargumentexception(e.tostring()); } } }
我们可以根据代理类的字节码进行反编译,可以得到如下结果,其中helloworld只有sayhello方法,但是代理类中有四个方法 包括了object上的三个方法:equals,tostring,hashcode。
代理的大概结构包括4部分:
- 静态字段:被代理的接口所有方法都有一个对应的静态方法变量;
- 静态块:主要是通过反射初始化静态方法变量;
- 具体每个代理方法:逻辑都差不多就是 h.invoke,主要是调用我们定义好的invocatinohandler逻辑,触发目标对象target上对应的方法;
- 构造函数:从这里传入我们invocationhandler逻辑;
package com.sun.proxy; import com.yao.helloworld; import java.lang.reflect.invocationhandler; import java.lang.reflect.method; import java.lang.reflect.proxy; import java.lang.reflect.undeclaredthrowableexception; public final class $proxy0 extends proxy implements helloworld { private static method m1; private static method m3; private static method m2; private static method m0; public $proxy0(invocationhandler var1) throws { super(var1); } public final boolean equals(object var1) throws { try { return ((boolean)super.h.invoke(this, m1, new object[]{var1})).booleanvalue(); } catch (runtimeexception | error var3) { throw var3; } catch (throwable var4) { throw new undeclaredthrowableexception(var4); } } public final void sayhello() throws { try { super.h.invoke(this, m3, (object[])null); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } public final string tostring() throws { try { return (string)super.h.invoke(this, m2, (object[])null); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } public final int hashcode() throws { try { return ((integer)super.h.invoke(this, m0, (object[])null)).intvalue(); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } static { try { m1 = class.forname("java.lang.object").getmethod("equals", new class[]{class.forname("java.lang.object")}); m3 = class.forname("com.yao.helloworld").getmethod("sayhello", new class[0]); m2 = class.forname("java.lang.object").getmethod("tostring", new class[0]); m0 = class.forname("java.lang.object").getmethod("hashcode", new class[0]); } catch (nosuchmethodexception var2) { throw new nosuchmethoderror(var2.getmessage()); } catch (classnotfoundexception var3) { throw new noclassdeffounderror(var3.getmessage()); } } }
常见问题:
1.tostring() hashcode() equal()方法 调用逻辑:这个三个object上的方法,如果被调用将和其他接口方法方法处理逻辑一样,都会经过invocationhandler逻辑,从上面的字节码结果就可以明显看出。其他object上的方法将不会走代理处理逻辑,直接走proxy继承的object上方法逻辑。
2.interface 含有equals,tostring hashcode方法时,和处理普通接口方法一样,都会走invocation handler逻辑,以目标对象重写的逻辑为准去触发方法逻辑;
3.interface含有重复的方法签名,以接口传入顺序为准,谁在前面就用谁的方法,代理类中只会保留一个,不会有重复的方法签名;
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
上一篇: 真正的获取客户端真实IP地址及利弊分析
下一篇: asp.net url分页类代码