作为每个Java开发都知道,但又很少能说完全清楚的技术:动态代理。
今天,我们就彻底搞明白它!
深入JDK动态代理实现
基本使用
没有花里胡哨的,就下面的代码:
UserServiceuserService=newUserServiceImpl();UserServiceo=(UserService)Proxy.newProxyInstance(UserService.class.getClassLoader(),newClass[]{UserService.class},newInvocationHandler(){
OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println("--before--");Objectinvoke=method.invoke(userService,args);System.out.println("--after--");returninvoke;}});可以看出,关键在于代理的创建:Proxy.newProxyInstance。后面我们也以这里为入口深入。
关键步骤解析
生成代理类
生成代理类是动态代理中的最复杂的步骤,而真正生成代理类字节码的方法位于:java.lang.reflect.Proxy.ProxyClassFactory#apply。
精简一下方法,主要有以下几个步骤:
//前置校验interfaceClass=Class.forName(intf.getName(),false,loader);if(xxx){thrownewIllegalArgumentException(xxx);}...//数据准备intaccessFlags=Modifier.PUBLIC
Modifier.FINAL;...StringproxyName=proxyPkg+proxyClassNamePrefix+num;...//生成代理类bytesbyte[]proxyClassFile=ProxyGenerator.generateProxyClass(proxyName,interfaces,accessFlags);...//加载returndefineClass0(loader,proxyName,proxyClassFile,0,proxyClassFile.length);
生成代理类与加载是动态代理的核心。
组装ProxyMethod
这里只是将收集到的方法元信息封装为sun.misc.ProxyGenerator.ProxyMethod类存储,并没有真正的生成code。
这里还特地也保存了了hashCode,equals,toString方法。
addProxyMethod(hashCodeMethod,Object.class);addProxyMethod(equalsMethod,Object.class);addProxyMethod(toStringMethod,Object.class);...sigmethods.add(newProxyMethod(name,parameterTypes,returnType,exceptionTypes,fromClass));
组装FieldInfo/MethodInfo
添加构造方法,并根据上一步组装好的ProxyMethod添加成员属性。
在这一步中,会调用sun.misc.ProxyGenerator.ProxyMethod#generateMethod方法,为其生成code。
//生成构造方法this.methods.add(this.generateConstructor());...//生成成员属性fields.add(newFieldInfo(pm.methodFieldName,"Ljava/lang/reflect/Method;",ACC_PRIVATE
ACC_STATIC));//添加方法methods.add(pm.generateMethod());...//添加静态初始化代码块methods.add(generateStaticInitializer());
构造最终类
根据上面几步收集到的信息,生成最终byte数组。
//u4magic;dout.writeInt(0xCAFEBABE);//u2minor_version;dout.writeShort(CLASSFILE_MINOR_VERSION);//u2major_version;dout.writeShort(CLASSFILE_MAJOR_VERSION);cp.write(dout);//(writeconstantpool)//u2access_flags;dout.writeShort(accessFlags);//u2this_class;dout.writeShort(cp.getClass(dotToSlash(className)));//u2super_class;dout.writeShort(cp.getClass(superclassName));...
最终结果
通过配置sun.misc.ProxyGenerator.saveGeneratedFiles=true可以将生成的代理类字节码保存下来。
删除了我们无需关心hashCode,toString等方法后,反编译结果如下:
package