本文共 12470 字,大约阅读时间需要 41 分钟。
package cn.edu.hpu.proxy;import java.lang.reflect.Method;//方法调用的处理器public interface InvocationHandler { //你只要给我一个Method方法,我就能对Method进行处理 //做处理的方式是由实现它的子类来决定 //参数:Object o,Method m,即invoke()内要调用对象Object o的Method m方法 public void invoke(Object o,Method m);}我们再写一个时间方法的处理类,实现InvocationHandler接口:
package cn.edu.hpu.proxy;import java.lang.reflect.Method;public class TimeHandler implements InvocationHandler{ private Object target; public TimeHandler(Object target) { super(); this.target=target; } //参数是一个方法,我们可以在invoke里在Method方法执行的前后加处理逻辑 @Override public void invoke(Object o,Method m){ long start=System.currentTimeMillis(); System.out.println("开始时间:"+start+"ms"); try { m.invoke(target);//执行target类的m方法 } catch (Exception e) { e.printStackTrace(); } long end=System.currentTimeMillis(); System.out.println("运行时间:"+(end-start)+"ms"); }}这里我们写了一个时间处理的方法调用的处理器 下面就来看我们之前的那段动态代码如何生成。先回顾一下之前的Proxy代码:
package cn.edu.hpu.proxy;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;public class Proxy { //这个方法用来产生新的代理类 //参数:告诉程序使用实现哪个接口的动态代理 public static Object newProxyInstance(Class infce) throws Exception{ String methodStr=""; String rt="\r\n"; //使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法 Method[] methods=infce.getMethods(); for (Method m:methods) { methodStr += rt+" @Override"+rt+ " public void "+m.getName()+"() {"+rt+ " this.before();"+ rt + " t."+m.getName()+"();"+ rt + " this.after();"+ rt + " }"; //返回值通过反射机制也可以拿到,但是比较麻烦,我们这里暂时用void代替 } String src= "package cn.edu.hpu.proxy;"+ rt + "public class TankTimeProxy implements "+infce.getName()+"{"+ rt + " Moveable t;"+ rt + " long start;"+ rt + " long end;"+ rt + " public TankTimeProxy(Moveable t) {"+ rt + " super();"+ rt + " this.t = t;"+ rt + " }"+ rt + " public void before(){"+ rt + " start=System.currentTimeMillis();"+ rt + " System.out.println(\"开始时间:\"+start+\"ms\");"+ rt + " }"+ " public void after(){"+ rt + " end=System.currentTimeMillis();"+ rt + " System.out.println(\"运行时间:\"+(end-start)+\"ms\");"+ rt + " }"+ rt + methodStr+ rt + "}"; //拿到当前项目的根目录:System.getProperty("user.dir")); String fileName=System.getProperty("user.dir") +"/src/cn/edu/hpu/proxy/TankTimeProxy.java"; //我们把src的源码写入自己创建的File文件中去 File f=new File(fileName); FileWriter fw=new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac) JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化) StandardJavaFileManager fileMgr= compiler.getStandardFileManager(null, null, null); //通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中 //Iterable是一个数组,用它可以进行迭代 Iterable units=fileMgr.getJavaFileObjects(fileName); //参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件) CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units); //进行编译 t.call(); fileMgr.close(); //把编译好的.class文件加载到内存中 //urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类) URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")}; //ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类 URLClassLoader ul=new URLClassLoader(urls); Class c=ul.loadClass("cn.edu.hpu.proxy.TankTimeProxy"); System.out.println(c); //得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型 Constructor ctr=c.getConstructor(Moveable.class); Object m=ctr.newInstance(new Tank()); return m; }}这一块我们就可以通过实现InvocationHandler接口来完成:
package cn.edu.hpu.proxy;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;public class Proxy { //这个方法用来产生新的代理类 //参数:告诉程序使用实现哪个接口的动态代理 public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{ String methodStr=""; String rt="\r\n"; //使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法 Method[] methods=infce.getMethods(); for (Method m:methods) { methodStr += rt+" @Override"+rt+ " public void "+m.getName()+"() {"+rt+ " Method md=null;"+rt+ " try{"+rt+ " md="+infce.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+ " h.invoke(this,md);"+rt+ " }catch(Exception e){"+rt+ " e.printStackTrace();}"+rt+ " }"; } String src= "package cn.edu.hpu.proxy;"+ rt + "import java.lang.reflect.Method;"+ rt + "public class TankTimeProxy implements "+infce.getName()+"{"+ rt + " cn.edu.hpu.proxy.InvocationHandler h;"+ rt + " public TankTimeProxy(InvocationHandler h) {"+ rt + " super();"+ rt + " this.h = h;"+ rt + " }"+ rt + methodStr+ rt + "}"; //拿到当前项目的根目录:System.getProperty("user.dir")); String fileName=System.getProperty("user.dir") +"/src/cn/edu/hpu/proxy/TankTimeProxy.java"; //我们把src的源码写入自己创建的File文件中去 File f=new File(fileName); FileWriter fw=new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac) JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化) StandardJavaFileManager fileMgr= compiler.getStandardFileManager(null, null, null); //通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中 //Iterable是一个数组,用它可以进行迭代 Iterable units=fileMgr.getJavaFileObjects(fileName); //参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件) CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units); //进行编译 t.call(); fileMgr.close(); //把编译好的.class文件加载到内存中 //urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类) URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")}; //ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类 URLClassLoader ul=new URLClassLoader(urls); Class c=ul.loadClass("cn.edu.hpu.proxy.TankTimeProxy"); System.out.println(c); //得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型 Constructor ctr=c.getConstructor(InvocationHandler.class); Object m=ctr.newInstance(h);//之前的Tank()改为InvocationHandler return m; }}测试:
package cn.edu.hpu.proxy;public class Client { public static void main(String[] args) throws Exception { Tank t=new Tank(); InvocationHandler h=new TimeHandler(t); Moveable m=(Moveable)Proxy.newProxyInstance(Moveable.class,h); m.move(); }}生成的TankTimeProxy类:
package cn.edu.hpu.proxy;import java.lang.reflect.Method;public class TankTimeProxy implements cn.edu.hpu.proxy.Moveable{ cn.edu.hpu.proxy.InvocationHandler h; public TankTimeProxy(InvocationHandler h) { super(); this.h = h; } @Override public void move() { Method md=null; try{ md=cn.edu.hpu.proxy.Moveable.class.getMethod("move"); h.invoke(this,md); }catch(Exception e){ e.printStackTrace();} }}测试结果: class cn.edu.hpu.proxy.TankTimeProxy 开始时间:1436446021225ms 坦克正在移动中... 运行时间:8014ms
可能大家有点晕,看看运行逻辑图:
其实我们的TankTimeProxy就是JDK动态代理中的$Proxy1类 为了更贴近JDK的动态代理,我们吧原来的TankTimeProxy名字改为$proxy1,即生成的你看不见的加了前后逻辑的动态代理类。package cn.edu.hpu.proxy;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import javax.tools.JavaCompiler;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;public class Proxy { //这个方法用来产生新的代理类 //参数:告诉程序使用实现哪个接口的动态代理 public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{ String methodStr=""; String rt="\r\n"; //使用了Java的反射机制中的getMethods();方法来得到一个类的所有方法 Method[] methods=infce.getMethods(); for (Method m:methods) { methodStr += rt+" @Override"+rt+ " public void "+m.getName()+"() {"+rt+ " Method md=null;"+rt+ " try{"+rt+ " md="+infce.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+ " h.invoke(this,md);"+rt+ " }catch(Exception e){"+rt+ " e.printStackTrace();}"+rt+ " }"; } String src= "package cn.edu.hpu.proxy;"+ rt + "import java.lang.reflect.Method;"+ rt + "public class $Proxy1 implements "+infce.getName()+"{"+ rt + " cn.edu.hpu.proxy.InvocationHandler h;"+ rt + " public $Proxy1(InvocationHandler h) {"+ rt + " super();"+ rt + " this.h = h;"+ rt + " }"+ rt + methodStr+ rt + "}"; //拿到当前项目的根目录:System.getProperty("user.dir")); String fileName=System.getProperty("user.dir") +"/src/cn/edu/hpu/proxy/$Proxy1.java"; //我们把src的源码写入自己创建的File文件中去 File f=new File(fileName); FileWriter fw=new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //编译(getSystemJavaCompiler()拿到系统默认的Java编译器,其实就是javac) JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); //需要一个FileManager,用它来管理文件(参数1:诊断的监听器,参数2、3国际化) StandardJavaFileManager fileMgr= compiler.getStandardFileManager(null, null, null); //通过FileManager找到TankTimeProxy文件,然后放到一个Iterable中 //Iterable是一个数组,用它可以进行迭代 Iterable units=fileMgr.getJavaFileObjects(fileName); //参数:(输出位置,文件管理器对象,监听器,编译的时候指定的参数,用到那些class文件,需要编译哪些文件) CompilationTask t=compiler.getTask(null, fileMgr, null, null, null, units); //进行编译 t.call(); fileMgr.close(); //把编译好的.class文件加载到内存中 //urls指定.class文件所放的地方(使用URL还可以Load从网上传过来的类) URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")}; //ClassLoader指的是吧硬盘里的Java文件放到内存中的那些个类 URLClassLoader ul=new URLClassLoader(urls); Class c=ul.loadClass("cn.edu.hpu.proxy.$Proxy1"); System.out.println(c); //得到TankTimeProxy类的构造方法,构造方法的参数为Moveable类型 Constructor ctr=c.getConstructor(InvocationHandler.class); Object m=ctr.newInstance(h); return m; }}我们这么折腾半天,完成了什么工作呢? 那就是:可以对任意的对象、任意的方法,实现任意的代理。 我们下面来用一下我们自己写的Proxy,看看它是如何方便的: 首先我们写一个接口和接口的实现:
UserMgr.java:package cn.edu.hpu.ProxyTest;public interface UserMgr { void addUser();}UserMgrImpl.java:
package cn.edu.hpu.ProxyTest;public class UserMgrImpl implements UserMgr{ @Override public void addUser() { System.out.println("1:插入记录到user表"); System.out.println("2:做日志在另外一张表"); } }类似于JavaEE的东西,先不管,我们在addUser()执行了2个操作(由于没有连数据库,直接打印相当于操作了),现在,我们想控制这两个操作(1和2操作)是否同时完成,这叫控制"transaction"(或理解为方法前后做日志)。 原来需要在代码前后加代码,现在我们这么做: 创建一个新的类,叫TransactionHandler,让他去实现我们自己的InvocationHandler
package cn.edu.hpu.ProxyTest;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import cn.edu.hpu.proxy.InvocationHandler;public class TransactionHandler implements InvocationHandler{ private Object target; public TransactionHandler(Object target){ super(); this.target=target; } @Override public void invoke(Object o, Method m) { System.out.println("Transaction Start"); try { m.invoke(target); }catch (Exception e) { e.printStackTrace(); } System.out.println("Transaction Commit"); }}我们实现了一个Transaction的处理器。 测试:
package cn.edu.hpu.ProxyTest;import cn.edu.hpu.proxy.InvocationHandler;import cn.edu.hpu.proxy.Proxy;public class Client { public static void main(String[] args) throws Exception { UserMgr userMgr=new UserMgrImpl(); InvocationHandler h=new TransactionHandler(userMgr); UserMgr u=(UserMgr)Proxy.newProxyInstance(UserMgr.class, h); u.addUser(); }}测试结果: Transaction Start 1:插入记录到user表 2:做日志在另外一张表 Transaction Commit 我两个代理之间也是可以叠加的,可以相互替换的,而且是可插拔的,什么时候不想用哪个代理了,就在配置文件中去掉就行了。这就是动态代理的巨大作用。这就是为什么Spring可以去管理AOP,去管理Transaction,还可以管理其他各种各样的内容。 我们与JDK的动态代理比较一下,JDK的动态代理由两个类构成,第一个叫Proxy: 常用方法: newProxyInstance(ClassLoader loder,Class<?>[] interfaces,nvocationHandler h) 第一个参数指明需要用哪种ClassLoader把代理类的对象load到内存中。我们自己写的Proxy用 的是URLClassLoad。后面两个参数和我们自己写的newProxyInstance一样。 再看JDK的InvocationHandler: 是一个接口,里面有一个方法: invoke(Object proxy,Method method,Object[] args) 前两个参数和我们自己写的InvocationHandler一样,最后一个参数指的是调用Method方法时所需要的参数。 动态代理还有一个好处,就是当一个被代理类中有许多的方法需要加前后逻辑,我们仍然只需要按照刚刚的步奏先创建被代理类和InvocationHandler,然后作为参数传进Proxy.newProxyInstance();中即可了。如果按照第一篇总结我们写的那个聚合的,要自己亲手加代码。 动态代理有什么用?事务处理,权限控制,AOP,安全,日志,可以在不该变代码的情况下插入其他功能。
至此,动态代理的设计模式已经总结完毕,大家可以自己动手操作一下,与JDK自己的动态代理作比较,思路会更加清晰。同时感谢马士兵老师的视频教程。
转载请注明出处: