马士兵设计模式-动态代理
1.class Tank实现Moveable接口,实现了move()方法,现在在move方法的前后记录这个方法的执行时间:
com.cy.proxy.Moveable:
package com.cy.proxy; public interface Moveable { public void move(); }
com.cy.proxy.Tank:
package com.cy.proxy; import java.util.Random; public class Tank implements Moveable{ @Override public void move() { long start = System.currentTimeMillis(); System.out.println("Tank Moving..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
2.但是现在假如不知道Tank类的源码,不能再其中的move()方法中直接添加代码的情况下,怎么知道move方法的执行时间?
1)Tank2继承Tank,重写其中的move()方法,在调用super.move()方法的前后添加逻辑,来记录时间:
package com.cy.proxy; public class Tank2 extends Tank{ @Override public void move() { long start = System.currentTimeMillis(); super.move(); long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
2)聚合,实现接口的方式在方法的前后记录时间,Tank3也实现Moveable接口,保存Tank类,每次调用Tank的move方法,在其前后加入逻辑:
package com.cy.proxy; public class Tank3 implements Moveable{ private Tank t; public Tank3(Tank t) { super(); this.t = t; } @Override public void move() { long start = System.currentTimeMillis(); t.move(); long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
1、2两种方式都实现了对class Tank中move()方法的代理,哪种方法好呢?---聚合的方式好些,继承的方式不灵活;
为什么?
比如还有个记录Tank move方法的日志代理:TankLogProxy:
package com.cy.proxy; public class TankLogProxy implements Moveable{ private Moveable t; public TankLogProxy(Moveable t) { super(); this.t = t; } @Override public void move() { System.out.println("Tank Start..."); t.move(); System.out.println("Tank Stop."); } }
package com.cy.proxy; public class TankTimeProxy implements Moveable{ private Moveable t; public TankTimeProxy(Moveable t) { super(); this.t = t; } @Override public void move() { long start = System.currentTimeMillis(); t.move(); long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
测试代码:
package com.cy.proxy; public class Client { public static void main(String[] args) { //日志代理,记录tank.move()执行时间 TankTimeProxy ttp = new TankTimeProxy(new Tank()); ttp.move(); } } Tank Moving... time: 967
package com.cy.proxy; public class Client { public static void main(String[] args) { //在时间外面加一层日志 TankTimeProxy ttp = new TankTimeProxy(new Tank()); TankLogProxy tlp = new TankLogProxy(ttp); tlp.move(); } } Tank Start... Tank Moving... time: 3631 Tank Stop.
TankTimeProxy、TankLogProxy、Tank都实现了Moveable接口,TankTimeProxy和TankLogProxy之间可以互相代理;
package com.cy.proxy; public interface Moveable { public void move(); public void stop(); }
package com.cy.proxy; import java.util.Random; public class Tank implements Moveable{ @Override public void move() { System.out.println("Tank Moving..."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void stop() { System.out.println("Tank Stopping..."); } }
package com.cy.proxy; public class TankTimeProxy implements Moveable{ private Moveable t; public TankTimeProxy(Moveable t) { super(); this.t = t; } @Override public void move() { long start = System.currentTimeMillis(); System.out.println("starttime: " + start); t.move(); long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } @Override public void stop() { long start = System.currentTimeMillis(); System.out.println("starttime: " + start); t.stop(); long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
package com.cy.proxy; public class Proxy { //用来产生新的代理类 public static Object newProxyInstance(){ String src = "package com.cy.proxy;" + "public class TankTimeProxy implements Moveable{" + "private Moveable t;" + "public TankTimeProxy(Moveable t) {" + "super();" + "this.t = t;" + "}" + "@Override" + "public void move() {" + "long start = System.currentTimeMillis();" + "System.out.println(\"starttime: \" + start);" + "t.move();" + "long end = System.currentTimeMillis();"+ "System.out.println(\"time: \" + (end-start));" + "}" + "}"; return null; } }
package com.cy.compiler.test; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import com.cy.proxy.Moveable; import com.cy.proxy.Tank; public class Test1 { public static void main(String[] args) throws Exception{ String rt = "\r\n"; String src = "package com.cy.proxy;" + rt + "public class TankTimeProxy implements Moveable{" + rt + " private Moveable t;" + rt + " public TankTimeProxy(Moveable t) {" + rt + " super();" + rt + " this.t = t;" + rt + " }" + rt + " @Override" + rt + " public void move() {" + rt + " long start = System.currentTimeMillis();" + rt + " System.out.println(\"starttime: \" + start);" + rt + " t.move();" + rt + " long end = System.currentTimeMillis();"+ rt + " System.out.println(\"time: \" + (end-start));" + rt + " }" + rt + "}"; //先把源代码写到下面: String fileName = System.getProperty("user.dir")+"/src/com/cy/proxy/TankTimeProxy.java"; //user.dir: 当前项目的根路径 //E:/jdbcWorkspace/Proxy/src/com/cy/proxy/TankTimeProxy.java File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //在程序中编译这段代码: //JavaCompiler java的编译器 //ToolProvider.getSystemJavaCompiler() 拿到系统当前默认的java编译器,其实就是javac JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //System.out.println(compiler.getClass().getName()); //com.sun.tools.javac.api.JavacTool StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); //通过fileMgr管理要编译的文件 Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(fileName); //拿到编译的内容 CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); //编译的任务 t.call(); //编译 fileMgr.close(); //生成了这个TankTimeProxy.class之后,将其load到内存,并且生成一个对象 URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "/src")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.cy.proxy.TankTimeProxy"); //class com.cy.proxy.TankTimeProxy //c.newInstance() 调用的是类TankTimeProxy参数为空的构造方法,但是类中没有此构造方法 //站在java虚拟机的角度,每一个类,每一个类里面的方法都是一个对象; Constructor ctr = c.getConstructor(Moveable.class); Moveable m = (Moveable)ctr.newInstance(new Tank()); m.move(); } } 打印结果如下: starttime: 1493223076248 Tank Moving... time: 861
Proxy中就可以通过Proxy.newProxyInstance()来返回一个代理对象了;
4)现在确实能产生一个动态的代理了,但是现在产生的代理只能够代理实现了Moveable接口的这样的一种代理;如果是实现了别的接口的就不行了,现在考虑能生成实现任意接口的代理类:
package com.cy.compiler.test; import java.lang.reflect.Method; public class Test2 { public static void main(String[] args) { //通过反射可以获取接口中有多少个方法 Method[] methods = com.cy.proxy.Moveable.class.getMethods(); for(Method m : methods){ System.out.println(m); //public abstract void com.cy.proxy.Moveable.move() } } }
b.代码实现,代理实现任意接口的类:
Proxy.java: newProxyInstance(Class infce):
package com.cy.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.JavaFileObject; 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 rt = "\r\n"; String methodStr = ""; Method[] methods = infce.getMethods(); for(Method m : methods){ methodStr += " @Override" + rt + " public void " + m.getName() + "() {" + rt + " long start = System.currentTimeMillis();" + rt + " System.out.println(\"starttime: \" + start);" + rt + " t." + m.getName() + "();" + rt + " long end = System.currentTimeMillis();"+ rt + " System.out.println(\"time: \" + (end-start));" + rt + " }" + rt; } String src = "package com.cy.proxy;" + rt + "public class TankTimeProxy implements "+infce.getName()+"{" + rt + " private Moveable t;" + rt + " public TankTimeProxy(Moveable t) {" + rt + " super();" + rt + " this.t = t;" + rt + " }" + rt + methodStr + "}"; //先把源代码写到下面: String fileName = "d:/src/com/cy/proxy/TankTimeProxy.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //在程序中编译这段代码: //JavaCompiler java的编译器 //ToolProvider.getSystemJavaCompiler() 拿到系统当前默认的java编译器,其实就是javac JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //System.out.println(compiler.getClass().getName()); //com.sun.tools.javac.api.JavacTool StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); //通过fileMgr管理要编译的文件 Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(fileName); //拿到编译的内容 CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); //编译的任务 t.call(); //编译 fileMgr.close(); //生成了这个TankTimeProxy.class之后,将其load到内存,并且生成一个对象 URL[] urls = new URL[] {new URL("file:/" + "d:/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.cy.proxy.TankTimeProxy"); //class com.cy.proxy.TankTimeProxy //c.newInstance() 调用的是类TankTimeProxy参数为空的构造方法,但是类中没有此构造方法 //站在java虚拟机的角度,每一个类,每一个类里面的方法都是一个对象; Constructor ctr = c.getConstructor(infce); Object m = ctr.newInstance(new Tank()); return m; } }
//上面的代码中修改了生成源码文件、编译class文件的存放位置;
//利用反射,动态生成实现接口方法的字符串;为了简单起见,没有模拟方法的参数和返回值;
c.测试代码:
package com.cy.proxy; public class Client { public static void main(String[] args) throws Exception{ Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class); m.move(); } } starttime: 1493228074841 Tank Moving... time: 5836
5)我们现在的Proxy能够对实现任意接口的类进行代理了,但是上面的Proxy中的生成逻辑的代码是固定的,只能是记录时间的代码,如果要生成一个记录日志的代理/权限的代理,就不行了;
package com.cy.proxy; import java.lang.reflect.Method; public interface InvocationHandler { public void invoke(Object o, Method m); //假设返回值void }
package com.cy.proxy; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler{ @Override public void invoke(Object o, Method m) { long start = System.currentTimeMillis(); System.out.println("starttime: " + start); try { m.invoke(o, new Object[]{}); //假设方法中没有参数 } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } } 我们可以对一个任意的方法进行自定义的处理;现在只要在调用给定的方法前后加上记录时间的逻辑就行了; Method m: 想要调用的那个方法;在调用这个方法的之前或者之后做一些事情; 方法的调用:对一个方法的调用,我们必须要知道这个方法的当前对象是谁,也就是this是谁; Object o: 对那个对象调用的m方法;即对o这个对象调用m方法;
现在考虑Proxy中动态的代码该怎么生成了:
Proxy.java:
package com.cy.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.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import javax.tools.JavaCompiler.CompilationTask; public class Proxy { /** * 用来产生新的代理类 * infce 产生哪个接口的动态代理 * InvocationHandler 要实现什么样的代理,对接口中的方法前后进行什么样的处理,记录时间or... */ public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception{ String rt = "\r\n"; String methodStr = ""; Method[] methods = infce.getMethods(); for(Method m : methods){ methodStr += " @Override" + rt + " public void " + m.getName() + "() {" + rt + " try{" + rt + " Method md = " + infce.getName() + ".class.getMethod(\"" +m.getName()+"\");" + rt + " h.invoke(this, md);" + rt + " }catch(Exception e){e.printStackTrace();}" + rt + " }" + rt; } String src = "package com.cy.proxy;" + rt + "import java.lang.reflect.Method;" + rt + "public class TankTimeProxy implements "+infce.getName()+"{" + rt + " public TankTimeProxy(InvocationHandler h) {" + rt + " this.h = h;" + rt + " }" + rt + " com.cy.proxy.InvocationHandler h;" + rt + methodStr + "}"; //先把源代码写到下面: String fileName = "d:/src/com/cy/proxy/TankTimeProxy.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //在程序中编译这段代码: //JavaCompiler java的编译器 //ToolProvider.getSystemJavaCompiler() 拿到系统当前默认的java编译器,其实就是javac JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //System.out.println(compiler.getClass().getName()); //com.sun.tools.javac.api.JavacTool StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); //通过fileMgr管理要编译的文件 Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(fileName); //拿到编译的内容 CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); //编译的任务 t.call(); //编译 fileMgr.close(); //生成了这个TankTimeProxy.class之后,将其load到内存,并且生成一个对象 URL[] urls = new URL[] {new URL("file:/" + "d:/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.cy.proxy.TankTimeProxy"); //class com.cy.proxy.TankTimeProxy //c.newInstance() 调用的是类TankTimeProxy参数为空的构造方法,但是类中没有此构造方法 //站在java虚拟机的角度,每一个类,每一个类里面的方法都是一个对象; Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); return m; } }
TimeHandler.java:
package com.cy.proxy; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler{ private Object target; //被代理对象 public TimeHandler(Object target){ this.target = target; } /** * Object o这里没用到; * Proxy中的h.invoke(this, md)其实this没用到,this的指向是代理对象 可以print看下: com.cy.proxy.TankTimeProxy */ @Override public void invoke(Object o, Method m) { long start = System.currentTimeMillis(); System.out.println("starttime: " + start); System.out.println(o.getClass().getName()); //com.cy.proxy.TankTimeProxy try { m.invoke(target); //对被代理对象调用m方法 } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("time: " + (end-start)); } }
Client.java测试代码:
package com.cy.proxy; public class Client { public static void main(String[] args) throws Exception{ Tank t = new Tank(); //被代理对象 InvocationHandler h = new TimeHandler(t); //代理逻辑,这里是记录时间的逻辑 //Proxy.newProxyInstance(Moveable.class, h)返回一个代理对象,我们不知道名字 Moveable m = (Moveable) Proxy.newProxyInstance(Moveable.class, h); m.move(); } }
看下D:\src下面生成的代理对象:
package com.cy.proxy; import java.lang.reflect.Method; public class TankTimeProxy implements com.cy.proxy.Moveable{ public TankTimeProxy(InvocationHandler h) { this.h = h; } com.cy.proxy.InvocationHandler h; @Override public void move() { try{ Method md = com.cy.proxy.Moveable.class.getMethod("move"); h.invoke(this, md); }catch(Exception e){e.printStackTrace();} } }
5.JDK中的动态代理,和上面模拟的代码几乎一样:
Proxy:
6)使用JDK里面的Proxy、InvocationHandler写的动态代理的例子:
需求:在userDao的save、delete方法的前后加上日志记录:
UserDao.java:
package com.cy.aop; public interface UserDao { public int save(String user); public boolean delete(String user); }
UserDaoImpl.java:
package com.cy.aop; public class UserDaoImpl implements UserDao { @Override public int save(String user) { System.out.println("add User: " + user); return 1; } @Override public boolean delete(String user) { System.out.println("delete User: " + user); return true; } }
UserDaoLogHanler.java,在方法的前后加上记录日志的逻辑:
package com.cy.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class UserDaoLogHanler implements InvocationHandler { //target是被代理对象 private Object target; public UserDaoLogHanler(Object target){ this.target = target; } /** * 调用被代理对象实现接口UserDao的每个方法,save,delete都会调用invoke方法; * * 1.先加自己的业务逻辑 * 2.再调用被代理对象的对应方法; * 3.再加自己的业务逻辑也行。。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"----log start..."); /** * obj是target调用method的返回值 也就是public int save(String)方法的返回值int * method: 是要调用的被代理对象的方法; * args: 是method方法的参数 */ Object obj = method.invoke(target, args); System.out.println(method.getName()+"----log end."); return obj; //执行proxy.save,obj是1;执行proxy.delete,obj是true } }
UserService.java 测试代码:
package com.cy.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class UserService { /** * 1.构建被代理对象;userDao; * 2.把被代理对象交给handler; * 3.使用newProxyInstance产生代理对象;其中的参数: * 1)classloader: 必须和被代理对象使用同一个classloader; * 2)被代理对象使用的接口;jdk生成的代理对象会使用同样的接口; * 3)代理对象调用方法的时候是由handler来处理; */ public static void main(String[] args) { UserDao userDao = new UserDaoImpl(); InvocationHandler h = new UserDaoLogHanler(userDao); UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), h); proxy.save("小明"); proxy.delete("小明"); } /** * JDK中要给一个类实现动态代理: * 1.这个类必须实现一个接口,没有实现接口的类,JDK是产生不了动态代理的; */ }
console:
整个源码会上传到文件:Proxy.zip https://files.cnblogs.com/files/tenWood/Proxy.rar