设计模式-JDK动态代理的模拟
在设计模式-代理模式一文中我们了解到,jdk底层帮我们实现了一套动态代理的方式,即使用反射包下的Proxy类以及InvocationHandler接口,我们在写动态代理时直接拿来用就可以了,当然,JDK底层的Proxy实际是非常复杂的,所以本文只抽象出重要部分进行简单模拟来分析Proxy实现的大致原理
一、InvocationHandler模拟
/** * 模拟jdk动态代理的InvocationHandler接口 */ public interface InvocationHandler_simulate { public Object invoke(Object obj, Method method)throws Exception; }
二、Proxy模拟(核心)
/** * 模拟jdk动态代理的Proxy类 */ public class Proxy_simulate { public static Object newProxyInstance(Class interfaces,InvocationHandler_simulate h)throws Exception{ String methodStr = ""; //换行符 String rt = "\r\n"; //通过接口遍历所有方法 Method[] methods = interfaces.getMethods(); for(Method m:methods){ methodStr += "@Override"+rt+ "public void "+m.getName()+"(){"+rt+ "try{"+rt+ "Method md = "+interfaces.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+ "h.invoke(this,md);"+rt+ "}catch(Exception e){e.printStackTrace();}"+rt+ "}"; } //动态代理对象生成的源码模拟(此处需要注意package为我的工作空间的包名,若需要拷贝运行需将相应的包以及路径替换一下) String src = "package com.dynamicproxy_jdk_simulate;"+rt+ "import java.lang.reflect.Method;"+rt+ "public class $Proxy1 implements "+interfaces.getName()+"{"+rt+ " com.dynamicproxy_jdk_simulate.InvocationHandler_simulate h;"+rt+ " public $Proxy1(InvocationHandler_simulate h){" +rt+ " this.h = h;" +rt+ " }" +rt+ methodStr+ "}"; //动态代理对象生成的文件存放路径(若D盘不存在该路径请先创建) String fileName = "d:/src/com/dynamicproxy_jdk_simulate/$Proxy1.java"; File file = new File(fileName); FileWriter fileWriter = new FileWriter(file); fileWriter.write(src); fileWriter.flush(); fileWriter.close(); //此时.java文件已经生成,接下来就是使用编译器动态的编译该java文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null,null); Iterable iterable = fileManager.getJavaFileObjects(fileName); JavaCompiler.CompilationTask task = compiler.getTask(null,fileManager,null,null,null,iterable); task.call(); fileManager.close(); //编译完java文件后则将class文件加载到内存并创建实例 URL[] urls = new URL[]{new URL("file:/d:/src/")}; URLClassLoader classLoader = new URLClassLoader(urls); Class c = classLoader.loadClass("com.dynamicproxy_jdk_simulate.$Proxy1"); Constructor constructor = c.getConstructor(InvocationHandler_simulate.class); Object temp_obj = constructor.newInstance(h); return temp_obj; } }
三、测试-这部分和jdk动态代理相似
/** * 被代理类需要实现的接口 */ public interface A_Interface { public void method1(); }
/** * 被代理类 */ public class B_Source implements A_Interface { @Override public void method1() { System.out.println("原始方法"); } }
/** * 测试模拟的InvocationHandler_simulate */ public class C_MyInvocationHandler_Test implements InvocationHandler_simulate{ //被代理对象 private Object object; public C_MyInvocationHandler_Test(Object object){ this.object = object; } @Override public Object invoke(Object proxy, Method method) throws Exception{ System.out.println("模拟的动态代理开始前"); Object result = method.invoke(object); System.out.println("模拟的动态代理结束后"); return result; } }
/** * 测试类 */ public class D_Test { public static void main(String[] args) throws Exception{ //创建被代理对象 A_Interface source = new B_Source(); //创建与代理对象相关的Handler InvocationHandler_simulate invocationHandler = new C_MyInvocationHandler_Test(source); //创建动态代理对象 A_Interface proxy = (A_Interface) Proxy_simulate.newProxyInstance(A_Interface.class,invocationHandler); proxy.method1(); } } 结果: 模拟的动态代理开始前 原始方法 模拟的动态代理结束后
四、动态生成的$Proxy.java文件(即动态代理对象)
package com.dynamicproxy_jdk_simulate; import java.lang.reflect.Method; public class $Proxy1 implements com.dynamicproxy_jdk_simulate.A_Interface { com.dynamicproxy_jdk_simulate.InvocationHandler_simulate h; public $Proxy1(InvocationHandler_simulate h) { this.h = h; } @Override public void method1() { try { Method md = com.dynamicproxy_jdk_simulate.A_Interface.class.getMethod("method1"); h.invoke(this, md); } catch (Exception e) { e.printStackTrace(); } } }
五、总结
在第三步测试类的时候调用了Proxy_simulate的newInstance方法之后,会在磁盘上创建一个$Proxy.java文件以及编译后的$Proxy.class文件,也就是动态代理对象,有了这个对象,我们在第三步测试类调用的proxy.method1()实际就是调用了第四步$Proxy.java文件中的method1()方法,而h.invoke(this,md);就是通过创建该$Proxy对象时传递的Handler对象去调用对应的方法,这里我们传递的是C_MyInvocationHandler_Test这个Handler,所以执行的也就是这个Handler的invoke方法,有反射基础的同学应该比较好理解,最后,通过Proxy的模拟我们也可以看到,jdk的动态代理之所以要求被代理对象必须实现接口是因为其底层要通过遍历接口然后使用反射进行调用,程序运行结束后会在d盘对应的路径下找到动态生成的java文件以及class文件即动态代理对象,而jdk的代理对象则是生成在内存之中。