设计模式之代理模式(3)
观看这篇文章前,请先阅读设计模式之代理模式(1)
静态代理会发生类爆炸?那jdk的使用的动态代理到底是怎么做到的呢?我来大概模拟一下jdk的动态代理。
这是我的目录结构:(可先跳过代码,到最下面听下我的BB,在对照代码来看!)
我先来介绍一下这些兄弟:
Tank:
package cn.asto.proxy; import java.util.Random; public class Tank implements Moveable{ public void move(){ System.out.println("Tank is moving..."); try { Thread.sleep(new Random(47).nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
Moveable:
package cn.asto.proxy; public interface Moveable { public void move(); }
InvocationHandler(一个接口):
package cn.asto.proxy; import java.lang.reflect.Method; public interface InvocationHandler { public void invoke(Object o,Method m); }
TimeHandler(实现了InvocationHandler的接口)
package cn.asto.proxy; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler{ private Object target; public TimeHandler(Object target){ this.target = target; } @Override public void invoke(Object o, Method m) { long startTime = System.currentTimeMillis(); System.out.println("startTime:"+startTime); try { m.invoke(target,null); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("Time:"+(endTime-startTime)); } }
Proxy(总代理):
package cn.asto.proxy; 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.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Proxy { public static Object getProxyInstance(Class inface,InvocationHandler h) throws Exception{ String rt ="\r\t"; String methodStr = ""; // for(Method m:inface.getMethods()){ // methodStr+=" @Override" + rt+ // " public void " + m.getName() + "(){" + rt + // " long startTime = System.currentTimeMillis();"+ rt + // " System.out.println(\"startTime:\"+startTime);"+ rt + // " t." + m.getName() + "()" + ";"+ rt + // " long endTime = System.currentTimeMillis();"+ rt + // " System.out.println(\"Time:\"+(endTime-startTime));"+ rt + // // " }" + rt; // // } for(Method m : inface.getMethods()) { methodStr += "@Override" + rt + "public void " + m.getName() + "() {" + rt + " try {" + rt + " Method md = " + inface.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + " h.invoke(this, md);" + rt + " }catch(Exception e) {e.printStackTrace();}" + rt + "}"; } String str = "package cn.asto.proxy;"+ rt + "import java.lang.reflect.Method;" + rt + "public class TankTimeProxy implements " +inface.getName()+ "{"+ rt + " cn.asto.proxy.InvocationHandler h;" + rt + " public TankTimeProxy(InvocationHandler h) {"+ rt + " super();"+ rt + " this.h = h;"+ rt + "}"+ rt + methodStr+ "}"; String fileName =System.getProperty("user.dir")+"/src/cn/asto/proxy/TankTimeProxy.java"; FileWriter fw = new FileWriter(fileName); fw.write(str); fw.flush(); fw.close(); //compile JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(fileName); CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //load into memory and create an instance URL[] urls = new URL[]{(new URL("file:/"+System.getProperty("user.dir")+"/src/"))}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("cn.asto.proxy.TankTimeProxy"); System.out.println(c); Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = (Object)ctr.newInstance(h); return m; } }
Test1:
package cn.asto.compiler.test; import cn.asto.proxy.InvocationHandler; import cn.asto.proxy.Moveable; import cn.asto.proxy.Proxy; import cn.asto.proxy.Tank; import cn.asto.proxy.TimeHandler; public class Test1 { public static void main(String args[]) throws Exception{ Tank t = new Tank(); InvocationHandler h = new TimeHandler(t); Moveable m = (Moveable)Proxy.getProxyInstance(Moveable.class, h); m.move(); } }
大概思路就是将代理添加的逻辑代码和基类抽象出来,我们都知道所有的代理类(以及基类实现一个接口),并且为了提高逻辑代码的重用性,我们将这两部分抽象成InvocationHandler和XXX.class.(xxx表示代理和基类实现的接口,这里是指Moveable),
写一个TimeHandler类,传入一个代理或者基类,实现InvocationHandler接口,将实现类和xxx.class作为Proxy的构造参数传入,在Proxy大概做那么几件事情,动态生成代理类文件(.java),编译,将InvocationHandler的实现类传入到动态代理文件的构造函数中,构造代理类。(这里有一个回调的过程),让动态代理类去回调InvocationHandler的实现类中的具体逻辑代码(invoke方法),而invoke调用的就是传给InvocationHandler实现类的构造参数类的需要实现逻辑代码添加的方法的基础上,加上逻辑代码。
(总代理生成动态代理类,回调handler,handler回调move)
现在让我们来做一个实验,验证动态代理类可以实现代理任意的对象和任意的逻辑方法体(InvocationHandler)
新建一个User接口:
package cn.asto.proxy.user; public interface UserMgr { public void addUser(); }
建一个User类:
package cn.asto.proxy.user; public class User implements UserMgr{ @Override public void addUser() { System.out.println("添加用户"); } }
在建一个LoginHandler
package cn.asto.proxy.user; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import cn.asto.proxy.InvocationHandler; public class LoginHandler implements InvocationHandler{ private Object target; public LoginHandler(Object target) { super(); this.target = target; } @Override public void invoke(Object o, Method m) { System.out.println("登录成功"); try { m.invoke(target, null); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
在Client中进行调用:
package cn.asto.proxy.user; import cn.asto.proxy.Proxy; public class Client { public static void main(String args[]){ User u = new User(); LoginHandler l = new LoginHandler(u); try { UserMgr ur = (UserMgr)Proxy.getProxyInstance(UserMgr.class, l); ur.addUser(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
打印输出:
登录成功
添加用户
测试完成!
好让我来看看jdk的代理程序:
package cn.asto.jdk; import java.lang.reflect.Proxy; import cn.asto.proxy.user.UserMgr; public class Client { public static void main(String args[]){ User u = new User(); LoginHandler l = new LoginHandler(u); ClassLoader c = ClassLoader.getSystemClassLoader(); try { UserMgr ur = (UserMgr)Proxy.newProxyInstance(c,new Class[]{UserMgr.class}, l); ur.addUser(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package cn.asto.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class LoginHandler implements InvocationHandler { private Object target; public LoginHandler(Object target) { super(); this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("登录成功"); try { method.invoke(target, null); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
package cn.asto.jdk; import cn.asto.proxy.user.UserMgr; public class User implements UserMgr{ @Override public void addUser() { System.out.println("添加用户"); } }
package cn.asto.proxy.user; public interface UserMgr { public void addUser(); }
不用看了,基本和我实现得差不多。只不过换成了jdk的InvocationHandler和jdk的Proxy.newProxyInstance
总结:代理可以完成代码块的插拔叠加!什么意思呢?spring中aop不就是这样子吗? 拦截器? 登录校验?权限控制?