可以为任何接口生成任何代理的问题了,首先定义一个接口InvocationHandler,这么起名字是因为JDK提供的代理实例处理程序的接口也是InvocationHandler:
public interface InvocationHandler { void invoke(Object proxy, Method method) throws Exception; }
所以我们的Proxy类也要修改了,改为:
1 public class ProxyVersion_2 implements Serializable 2 { 3 private static final long serialVersionUID = 1L; 4 5 public static Object newProxyInstance(Class<?> interfaces, InvocationHandler h) throws Exception 6 { 7 Method[] methods = interfaces.getMethods(); 8 StringBuilder sb = new StringBuilder(1024); 9 10 sb.append("package com.xrq.proxy;\n\n"); 11 sb.append("import java.lang.reflect.Method;\n\n"); 12 sb.append("public class $Proxy1 implements " + interfaces.getSimpleName() + "\n"); 13 sb.append("{\n"); 14 sb.append("\tInvocationHandler h;\n\n"); 15 sb.append("\tpublic $Proxy1(InvocationHandler h)\n"); 16 sb.append("\t{\n"); 17 sb.append("\t\tthis.h = h;\n"); 18 sb.append("\t}\n\n"); 19 for (Method m : methods) 20 { 21 sb.append("\tpublic " + m.getReturnType() + " " + m.getName() + "()\n"); 22 sb.append("\t{\n"); 23 sb.append("\t\ttry\n"); 24 sb.append("\t\t{\n"); 25 sb.append("\t\t\tMethod md = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\");\n"); 26 sb.append("\t\t\th.invoke(this, md);\n"); 27 sb.append("\t\t}\n"); 28 sb.append("\t\tcatch (Exception e)\n"); 29 sb.append("\t\t{\n"); 30 sb.append("\t\t\te.printStackTrace();\n"); 31 sb.append("\t\t}\n"); 32 sb.append("\t}\n"); 33 } 34 sb.append("}"); 35 36 /** 生成一段Java代码 */ 37 String fileDir = System.getProperty("user.dir"); 38 String fileName = fileDir + "\\src\\com\\xrq\\proxy\\$Proxy1.java"; 39 File javaFile = new File(fileName); 40 Writer writer = new FileWriter(javaFile); 41 writer.write(sb.toString()); 42 writer.close(); 43 44 /** 动态编译这段Java代码,生成.class文件 */ 45 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 46 StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null); 47 Iterable<? extends JavaFileObject> iter = sjfm.getJavaFileObjects(fileName); 48 CompilationTask ct = compiler.getTask(null, sjfm, null, null, null, iter); 49 ct.call(); 50 sjfm.close(); 51 52 /** 将生成的.class文件载入内存,默认的ClassLoader只能载入CLASSPATH下的.class文件 */ 53 URL[] urls = new URL[] {(new URL("file:\\" + System.getProperty("user.dir") + "\\src"))}; 54 URLClassLoader ul = new URLClassLoader(urls); 55 Class<?> c = Class.forName("com.xrq.proxy.$Proxy1", false, ul); 56 57 /** 利用反射将c实例化出来 */ 58 Constructor<?> constructor = c.getConstructor(InvocationHandler.class); 59 Object obj = constructor.newInstance(h); 60 61 /** 使用完毕删除生成的代理.java文件和.class文件,这样就看不到动态生成的内容了 */ 62 File classFile = new File(fileDir + "\\src\\com\\xrq\\proxy\\$Proxy1.class"); 63 javaFile.delete(); 64 classFile.delete(); 65 66 return obj; 67 } 68 }
最明显的变化,代理的名字变了,从StaticProxy变成了$Proxy1,因为JDK也是这么命名的,用过代理的应该有印象。这个改进中拼接$Proxy1的.java文件是一个难点,不过我觉得可以不用纠结在这里,关注重点,看一下生成的$Proxy1.java的内容是什么:
public class $Proxy1 implements HelloWorld { InvocationHandler h; public $Proxy1(InvocationHandler h) { this.h = h; } public void print() { try { Method md = com.xrq.proxy.HelloWorld.class.getMethod("print"); h.invoke(this, md); } catch (Exception e) { e.printStackTrace(); } } }
看到,我们把对于待生成代理的接口方法的调用,变成了对于InvocationHandler接口实现类的invoke方法的调用(这就是动态代理最关键的一点),并传入了待调用的接口方法,这样不就实现了我们的要求了吗?我们InvocationHandler接口的实现类写invoke方法的具体实现,传入的第二个参数md.invoke就是调用被代理对象的方法,在这个方法前后都是代理内容,想加什么加什么,不就实现了动态代理了?所以,我们看一个InvocationHandler实现类的写法:
public class HelloInvocationHandler implements InvocationHandler { private Object obj; public HelloInvocationHandler(Object obj) { this.obj = obj; } public void invoke(Object proxy, Method method) { System.out.println("Before Hello World!"); try { method.invoke(obj, new Object[]{}); } catch (Exception e) { e.printStackTrace(); } System.out.println("After Hello World!"); } }
写个main函数测试一下:
public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); HelloWorld helloWorldImpl = new HelloWorldImpl(); InvocationHandler ih = new HelloInvocationHandler(helloWorldImpl); HelloWorld helloWorld = (HelloWorld)ProxyVersion_2.newProxyInstance(HelloWorld.class, ih); System.out.println("动态生成代理耗时:" + (System.currentTimeMillis() - start) + "ms"); helloWorld.print(); System.out.println(); }
运行结果为:
动态生成代理耗时:351ms Before Hello World! Hello World After Hello World!