深入JDK动态代理实现
基本使用
没有花里胡哨的,就下面的代码:
UserServiceuserService=newUserServiceImpl();
UserServiceo=(UserService)Proxy.newProxyInstance(UserService.class.getClassLoader(),newClass[]{UserService.class},newInvocationHandler(){
Override
publicObjectinvoke(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;
...
//生成代理类bytes
byte[]proxyClassFile=ProxyGenerator.generateProxyClass(
proxyName,interfaces,accessFlags);
...
//加载
returndefineClass0(loader,proxyName,
proxyClassFile,0,proxyClassFile.length);
生成代理类与加载是动态代理的核心。
组装ProxyMethod
Step1:AssembleProxyMethodobjectsforallmethodstogenerateproxydispatchingcodefor.
这里只是将收集到的方法元信息封装为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
Step2:AssembleFieldInfoandMethodInfostructsforalloffieldsandmethodsintheclasswearegenerating.
添加构造方法,并根据上一步组装好的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());
构造最终类
Step3:Writethefinalclassfile.
根据上面几步收集到的信息,生成最终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