实现JDK动态代理
实现JDK动态代理
在分析完JDK动态代理的实现之后,我们自己动手实现一个JDK动态代理。
实现MyProxy
首先,我们要创建一个MyProxy类,并提供一个newProxyInstance方法,如代码清单6-6所示。
代码清单6-6 MyProxy类
public class MyProxy { protected InvocationHandler h; private MyProxy(){ } protected MyProxy(InvocationHandler h) { this.h = h; } public static Objects newProxyInstance(Objects proxy,Class<?>[] interfaces, InvocationHandler h) { return null; } }
InvocationHandler我们还是使用JDK提供的,不需要自己创建一个InvocationHandler接口。MyProxy类的构造方法的访问标志声明为protected,要求子类必须提供一个带一个参数且参数类型为InvocationHandler的构造方法。
newProxyInstance方法要做的事情就是创建一个代理类,并使用类加载器加载这个代理类,最后通过反射创建一个代理类实例。类加载器我们使用自定义类的加载器。
实现MyProxyFactory
现在newProxyInstance方法我们还实现不了,还需要创建一个工厂类MyProxyFactory,用于动态生成代理类,只要传递需要生成的代理类的类名以及代理类需要实现的接口就能获取生成的代理类的字节数组。MyProxyFactory的实现如代码清单6-7所示。
代码清单6-7 MyProxyFactory类
public class MyProxyFactory { public static byte[] createProxyClass(String className, Class<?>[] interfaces) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); cw.visit(Opcodes.V1_8, ACC_PUBLIC, className, null, Type.getInternalName(MyProxy.class), getInternalNames(interfaces)); // 添加带参构造方法,调用父类MyProxy的带参构造方法 createInitMethod(cw); // 实现接口的方法 for (Class<?> interfaceClass : interfaces) { implInterfaceMethod(cw, className, interfaceClass); } // 添加静态代码块,获取method addStaticBlock(cw, className, interfaces); cw.visitEnd(); // 获取生成的代理类的字节数组 return cw.toByteArray(); } }
在MyProxyFactory类的createProxyClass方法中,我们使用ASM框架生成代理类。首先使用ClassWriter创建一个类,调用ClassWriter实例的visit方法设置类的class文件结构版本号、访问标志、类名、类签名、父类名、实现的接口。接着调用createInitMethod方法为该类添加一个带参构造方法,也就是类的实例初始化方法,参数类型为InvocationHandler。接着遍历需要实现的接口,调用implInterfaceMethod实现接口的每个方法。我们不考虑代理toString等Object类的方法。最后调用addStaticBlock方法为代理类添加一个静态代码块,在静态代码块中通过反射获取代理类实现的所有接口方法的Method对象。
createInitMethod方法的实现如代码清单6-8所示。
代码清单6-8 createInitMethod方法
private static void createInitMethod(ClassWriter cw) { MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); // 调用父类的构造方法 mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(MyProxy.class), "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); }
在createInitMethod方法中,我们先调用ClassWriter的visitMethod方法为代理类创建一个<init>方法,方法的描述符为“<Ljava/lang/reflect/InvocationHandler;>V”,就是为代理类添加一个带参数且参数类型为InvocationHandler的构造方法。接着调用MethodVisitor的API为方法添加调用父类构造方法的字节码指令。
因为是调用父类的带一个参数且参数类型为InvocationHandler的构造方法,所以调用方法需要两个参数,即this引用和InvocationHandler实例的引用。首先调用MethodVisitor的visitVarInsn方法为该方法生成将这两个参数放入操作数栈顶的字节码指令,接着调用visitMethodInsn方法为该方法生成调用父类方法的字节码指令。由于是调用父类的构造方法,因此使用invokespecial指令。
调用visitMethodInsn方法生成调用父类方法的字节码指令需要注意的是,第二个参数传父类的内部类名。内部类名就是将类名中的”.”替换为“/”后的类名。
implInterfaceMethod方法的实现如代码清单6-9所示。
代码清单6-9 implInterfaceMethod方法
private static void implInterfaceMethod(ClassWriter cw, String className, Class<?> interfaceClass) { Method[] methods = interfaceClass.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 给代理类添加静态字段 cw.visitField(ACC_PRIVATE | ACC_STATIC, "_" + interfaceClass.getSimpleName() + "_" + i, Type.getDescriptor(Method.class), null, null); // 给实现类实现该接口方法 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, new String[]{Type.getInternalName(Exception.class)}); mv.visitCode(); // 获取父类的字段,字段名为h,类型为InvocationHandler mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, Type.getInternalName(MyProxy.class), "h", Type.getDescriptor(InvocationHandler.class)); // 准备调用InvocationHandler的invoke方法的三个参数 // 第一个参数 mv.visitVarInsn(ALOAD, 0); // 第二个参数。获取静态字段 mv.visitFieldInsn(GETSTATIC, className, "_" + interfaceClass.getSimpleName() + "_" + i, Type.getDescriptor(Method.class)); // 第三个参数,将当前方法的参数构造成数组 int paramCount = method.getParameterCount(); if (paramCount == 0) { mv.visitInsn(ACONST_NULL); } else { // 数组大小 switch (paramCount) { case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, paramCount); } // 创建数组 mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class)); // 为数组元素赋值 for (int index = 1; index <= paramCount; index++) { mv.visitInsn(DUP); switch (index - 1) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, i - 1); break; } // 暂不考虑参数类型为基本数据类型的情况 mv.visitVarInsn(ALOAD, index); mv.visitInsn(AASTORE); } } // 调用InvocationHandler的invoke方法 mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(InvocationHandler.class), "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); // 添加return指令 addReturnInstruc(mv, method.getReturnType()); mv.visitFrame(F_FULL, 0, null, 0, null); mv.visitMaxs(1, 1); mv.visitEnd(); } }
在implInterfaceMethod方法中,我们通过反射获取接口声明的方法,实现接口的每个方法。在为代理类实现接口方法之前,先为代理类添加一个类型为Method的静态字段,保存该Method对象的引用,在调用InvocationHandler的invoke方法时需要用到。
静态字段的字段名生成格式为:
“_”+ 接口的简短名称 +“_”+ 反射获取接口方法的顺序
使用这个格式生成字段名,目的是不让字段名称重复。
静态字段的赋值在静态代码块中赋值,即代码清单6-7中调用的addStaticBlock方法,使用这个字段名称生成公式也方便在addStaticBlock方法中为静态字段赋与正确的值。
实现接口的某个方法,就是调用ClassWriter的visitMethod方法为代理类添加一个方法,方法名称与方法描述符都与该接口方法相同。当然,也可以给方法添加一个@Override注解,但这在字节码编程中没有多大意义。
如果想要为方法添加throws关键字,只需要在调用ClassWriter的visitMethod方法时,最后一个参数传递方法可能抛出的异常类型的内部类名。考虑到方法可能抛出异常,我们为方法添加抛出异常类型为Exception的Exceptions属性,即为方法添加throws关键字。
在implInterfaceMethod方法中,实现接口的每个方法都是先获取父类MyProxy的字段名为h的字段,然后调用字段h(InvocationHandler)的invoke方法。在调用invoke方法之前,需要先准备好调用invoke方法所需的参数。也就是将this引用放入操作数栈顶、将方法的Method对象放入操作数栈顶、将方法参数包装为一个数组对象放入操作数栈顶。
在实现将方法参数包装为一个数组对象时,我们并未考虑方法参数类型如果是基本数据类型的情况。因为数组元素类型为Object类型,如果想要将基本数据类型放入数组中,需要先将基本数据类型转为引用类型。比如int类型,我们可以调用Integer类的valueOf方法将int类型转为Integer类型。
在生成调用InvocationHandler的invoke方法的字节码指令之后,我们还调用了addReturnInstruc方法,该方法的作用是为我们实现的方法添加一条返回指令。addReturnInstruc方法的实现如代码清单6-10所示。
代码清单6-10 addReturnInstruc方法
private static void addReturnInstruc(MethodVisitor mv, Class returnType) { if (returnType == void.class) { mv.visitInsn(RETURN); } else if (returnType == int.class) { // 先将InvocationHandler的invoke方法返回值由Object类型转为Integer类型 mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Integer.class)); // 调用Integer的intValue方法 mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false); mv.visitInsn(IRETURN); } // .... else { mv.visitTypeInsn(CHECKCAST, Type.getInternalName(returnType)); mv.visitInsn(ARETURN); } }
addReturnInstruc实现根据反射获取到的方法返回值类型,添加对应的返回指令:
◆当方法返回值类型为void时,使用return指令。
◆当方法返回指令为基本数据类型时,可先添加将包装类型转为基本数据类型的字节码指令,再添加对应的基本数据类型的返回指令。如调用对应的包装类型的xxxValue方法将InvocationHandler的invoke方法返回的包装类实例转为基本数据类型值。
◆当方法返回值类型为引用类型时,使用areturn指令,但在添加areturn指令之前,需要先添加一条类型检测指令,实现强制类型转换。因为InvocationHandler的invoke方法返回值类型是Object,所以需要进行强制类型转换。
在implInterfaceMethod方法中,我们调用addReturnInstruc方法为代理方法添加返回指令之后,还调用了MethodVisitor的visitMaxs方法设置局部变量表和操作数栈的大小。由于创建ClassWriter对象时,我们传递的参数是COMPUTE_MAXS | COMPUTE_FRAMES,因此在调用MethodVisitor的visitMaxs方法设置操作数栈和局部变量表的大小时,我们可以随便传递一个数值,由ASM框架自动帮我们计算出方法所需要的操作数栈和局部变量表的大小。
现在我们看addStaticBlock方法的实现,该方法是为代理类生成静态代码块,在静态代码块中为静态字段赋值。如代码清单6-11所示。
代码清单6-11 addStaticBlock方法
private static void addStaticBlock(ClassWriter cw, String className, Class<?>[] interfaces) { // 给<clinit>方法添加static访问标志。对应java代码的静态代码块 MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv.visitCode(); // 遍历所有接口 for (Class cla : interfaces) { // 遍历接口的所有方法 Method[] methods = cla.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 调用Class的forName方法获取一个Class实例 String fieldName = "_" + cla.getSimpleName() + "_" + i; mv.visitLdcInsn(cla.getName()); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Class.class), "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); // 调用Class的getMethod方法,方法需要两个参数,一个是方法名称,一个是方法参数类型数组 // 参数1 mv.visitLdcInsn(method.getName()); // 参数2 // 创建数组,并为数组的每个元素赋值 Class[] methodParamTypes = method.getParameterTypes(); if (methodParamTypes.length == 0) { mv.visitInsn(ACONST_NULL); } else { // 数组大小 switch (methodParamTypes.length) { case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, methodParamTypes.length); } mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Class.class)); // 为数组元素赋值,数组元素类型为java.lang.Class for (int index = 0; index < methodParamTypes.length; index++) { mv.visitInsn(DUP); // 数组元素下标 switch (index) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, i); break; } mv.visitLdcInsn(methodParamTypes[index].getName()); // 调用forName获取参数的Class实例 mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Class.class), "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); // 存储到数组 mv.visitInsn(AASTORE); } } // 参数准备完毕,调用Class的getMethod方法获取Method mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Class.class), "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); // 为静态字段赋值 mv.visitFieldInsn(PUTSTATIC, className, fieldName, Type.getDescriptor(Method.class)); } } mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); }
addStaticBlock方法中,我们为代理类添加一个类初始化方法,并给方法添加访问标志为ACC_STATIC,对应Java代码的静态代码块。在实现接口方法的implInterfaceMethod方法中,我们已经为代理类持有各接口方法Method对象的引用添加对应的静态字段,因此在addStaticBlock方法中只需要为这些静态字段赋值。
因为Method对象并不是基本数据类型或者String类型,我们无法直接将反射获取的Method对象给代理类的静态字段赋值,而只能为其生成调用Class对象的getMethod方法获取Method对象,并将Method对象赋给静态字段的字节码指令。
使用MyProxy创建代理对象
现在我们继续完成MyProxy类的newProxyInstance方法,如代码清单6-12所示。
代码清单6-12完整的MyProxy类
public class MyProxy { protected InvocationHandler h; private final static AtomicInteger PROXY_CNT = new AtomicInteger(0); private MyProxy() { } protected MyProxy(InvocationHandler h) { this.h = h; } public static Object newProxyInstance(Class<?>[] interfaces, InvocationHandler h) throws Exception { String proxyClassName = "com/sun/proxy/$Proxy" + PROXY_CNT.getAndIncrement(); // 创建代理类 byte[] proxyClassByteCode = MyProxyFactory .createProxyClass(proxyClassName, interfaces); // 加载代理类 Class<?> proxyClass = ByteCodeUtils.loadClass(proxyClassName, proxyClassByteCode); // 反射创建代理类 Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); return constructor.newInstance(h); } }
在newProxyInstance方法中,先为代理类生成一个唯一的类名,接着调用MyProxyFactory的createProxyClass方法创建代理类。createProxyClass方法返回的是代理类的字节数组,需要使用自定义类加载器加载。加载完成后通过反射获取代理类的带一个参数且参数类型为InvocationHandler的构造器,调用构造器的newInstance方法创建代理对象。
我们将代码清单6-4中,使用JDK提供的Proxy类的newProxyInstance方法创建代理对象,改为使用MyProxy类的newProxyInstance方法创建动态代理对象。如代码清单6-13所示。
代码清单6-13 使用MyProxy创建动态代理对象
public static void main(String[] args) throws Exception { HttpRequestTemplate target = new HttpRequestTemplateImpl(); HttpRequestTemplate requestTemplate = (HttpRequestTemplate) MyProxy.newProxyInstance( new Class[]{HttpRequestTemplate.class}, new HttpRequestInvocationHandler(target)); }
使用MyProxy生成的HttpRequestTemplate接口的动态代理类如下。
public class $Proxy0 extends MyProxy implements HttpRequestTemplate { private static Method _HttpRequestTemplate_0 = Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequestTemplate") .getMethod("doGet", Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequest")); private static Method _HttpRequestTemplate_1 = Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequestTemplate") .getMethod("doPost", Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequest")); public $Proxy0(InvocationHandler var1) { super(var1); } public HttpResponse doGet(HttpRequest var1) throws Exception { return (HttpResponse)super.h.invoke(this, _HttpRequestTemplate_0, new Object[]{var1}); } public HttpResponse doPost(HttpRequest var1) throws Exception { return (HttpResponse)super.h.invoke(this, _HttpRequestTemplate_1, new Object[]{var1}); } }
我们所实现的动态代理类与JDK实现的动态代理类的区别,除了没有代理目标对象的toString、hashCode、equals方法之外,唯一的差别就是代理类实现的接口方法没有try-catch。
为代理方法添加try-catch
使用ASM生成try-catch代码块的语法为:
Label from = new Label();
Label to = new Label();
Label target = new Label();
// try开始
mv.visitLabel(from);
// try结束
mv.visitLabel(to);
// catch块开始
mv.visitLabel(target);
mv.visitTryCatchBlock(from, to, target, catch的异常类型);
调用MethodVisitor的visitLable方法用于记录在visitLable之后添加的第一条字节码指令的偏移量。实现一个try-catch代码块需要三个下标,对应Code属性的异常表中每一项的from、to、target的值。调用MethodVisitor的visitTryCatchBlock方法可为方法在Code属性的异常表中添加一个异常项,visitTryCatchBlock方法的最后一个参数是需要捕获的异常类型,如果传null则对应类型为“any”,也就是实现try-finally。
现在我们修改MyProxy类,为代理类实现的接口方法添加try-catch代码块,改造后的MyProxy类的implInterfaceMethod如代码清单6-14所示。
代码清单6-14 implInterfaceMethod方法
private static void implInterfaceMethod(ClassWriter cw, String className, Class<?> interfaceClass) { Method[] methods = interfaceClass.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 给代理类添加静态字段 cw.visitField(ACC_PRIVATE | ACC_STATIC, "_" + interfaceClass.getSimpleName() + "_" + i, Type.getDescriptor(Method.class), null, null); // 给实现类实现该接口方法 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, new String[]{Type.getInternalName(Exception.class)}); mv.visitCode(); Label from = new Label(); Label to = new Label(); Label target = new Label(); mv.visitLabel(from); // 获取父类的字段,字段名为h,类型为InvocationHandler mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, Type.getInternalName(MyProxy.class), "h", Type.getDescriptor(InvocationHandler.class)); // 准备调用InvocationHandler的invoke方法的三个参数 // 第一个参数 mv.visitVarInsn(ALOAD, 0); // 第二个参数。获取静态字段 mv.visitFieldInsn(GETSTATIC, className, "_" + interfaceClass.getSimpleName() + "_" + i, Type.getDescriptor(Method.class)); // 第三个参数,将当前方法的参数构造成数组 int paramCount = method.getParameterCount(); if (paramCount == 0) { mv.visitInsn(ACONST_NULL); } else { // 数组大小 switch (paramCount) { case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, paramCount); } // 创建数组 mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class)); // 为数组元素赋值 for (int index = 1; index <= paramCount; index++) { mv.visitInsn(DUP); switch (index - 1) { case 0: mv.visitInsn(ICONST_0); break; case 1: mv.visitInsn(ICONST_1); break; case 2: mv.visitInsn(ICONST_2); break; case 3: mv.visitInsn(ICONST_3); break; default: mv.visitVarInsn(BIPUSH, i - 1); break; } // 暂不考虑参数类型为基本数据类型的情况 mv.visitVarInsn(ALOAD, index); mv.visitInsn(AASTORE); } } // 调用InvocationHandler的invoke方法 mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(InvocationHandler.class), "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); // 添加return指令 addReturnInstruc(mv, method.getReturnType()); mv.visitLabel(to); mv.visitLabel(target); // 抛出异常 mv.visitInsn(ATHROW); // 添加异常捕获 mv.visitTryCatchBlock(from, to, target, Type.getInternalName(Exception.class)); mv.visitFrame(F_FULL, 0, null, 0, null); mv.visitMaxs(1, 1); mv.visitEnd(); } }
使用改造后的MyProxy生成的HttpRequestTemplate接口的动态代理类如下。
public class $Proxy0 extends MyProxy implements HttpRequestTemplate { private static Method _HttpRequestTemplate_0 = Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequestTemplate") .getMethod("doGet", Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequest")); private static Method _HttpRequestTemplate_1 = Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequestTemplate") .getMethod("doPost", Class.forName("com.wujiuye.asmbytecode.book.sixth.HttpRequest")); public $Proxy0(InvocationHandler var1) { super(var1); } public HttpResponse doPost(HttpRequest var1) throws Exception { try { return (HttpResponse)super.h.invoke(this, _HttpRequestTemplate_0, new Object[]{var1}); } catch (any var2) { throw var2; } } public HttpResponse doGet(HttpRequest var1) throws Exception { try { return (HttpResponse)super.h.invoke(this, _HttpRequestTemplate_1, new Object[]{var1}); } catch (any var2) { throw var2; } } }