java代理模式分析

首先来谈为什么需要代理。

比如我有一个接口如下

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public interface Moveable {  
  2.     void move();  
  3.       
  4. }  
它有一个move方法。现在我有这样一个需求,当我要通过子类实现该接口调用move方法的时候,我想在move方法的前后加一些逻辑
比如加日志、事务、权限等。我不想改变或者有时候我们没有源码没法改变move方法的时候,这时候我们可以让一个代理类帮我们做类似这样的事情。

因此我们先从静态代理说起。先建一个Moveable接口的实现类

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.util.Random;  
  2.   
  3.   
  4. public class Tank implements Moveable {  
  5.   
  6.     @Override  
  7.     public void move() {  
  8.           
  9.         System.out.println("Tank Moving...");  
  10.         try {  
  11.             Thread.sleep(new Random().nextInt(10000));  
  12.         } catch (InterruptedException e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.           
  16.     }  
  17.   
  18.       
  19.       
  20. }  

然后我们再建一个代理类LogProxy
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.   

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class LogProxy implements Moveable {  
  2.     private Moveable moveable;  
  3.     public LogProxy(Moveable moveable) {  
  4.         this.moveable = moveable;  
  5.     }  
  6.   
  7.     @Override  
  8.     public void move() {  
  9.         System.out.println("Tank move begin......");  
  10.         moveable.move();  
  11.         System.out.println("Tank move end......");  
  12.           
  13.     }  
  14.   
  15.       
  16.   
  17. }  

让它实现Moveable接口,并且持有Tank类的引用也就是实现Moveable接口的子类。重写move方法,并在其前后加日志逻辑。这样一个静态代理类就写好了。下面我们看客户

端调用代码。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Client {  
  2.     public static void main(String[] args) throws Exception {  
  3.         Tank t = new Tank();  
  4.         Moveable m = (Moveable)new LogProxy(t);  
  5.           
  6.         m.move();  
  7.     }  
  8. }  

打印结果:

Tank move begin......
Tank Moving...
Tank move end......


静态代理是有缺陷的,如果我想给Tank加一个其他逻辑的代理,那么我需要再新建一个代理类,并实现Moveable接口。这样不够灵活也不够方便。我们想要这样一个代理,它可以代理实现任何接口的对象的任何方法。而不是像静态代理那样只能代理实现Moveable接口的move方法,那么下面我们就来看动态代理。

在分析动态代理之前我们来看一下JDK自带的动态代理如何使用,还是以上面的Moveable接口、Tank类为例。我们先建一个日志逻辑处理类LogHandler实现InvocationHandler接口。


[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. public class LogHandler implements InvocationHandler {    
  4.     private Object target;  
  5.     public LogHandler(Object target) {    
  6.         this.target = target;     
  7.     }  
  8.     @override  
  9.     public Object invoke(Object proxy, Method method, Object[] args)  
  10.             throws Throwable {  
  11.         //目标方法前的逻辑  
  12.         System.out.println("start......" );  
  13.           
  14.         Object ret = null;  
  15.         try {  
  16.             //调用目标方法,本例中会调用move方法,这里用到反射技术  
  17.             ret = method.invoke(target, args);  
  18.               
  19.         }catch(Exception e) {  
  20.             e.printStackTrace();  
  21.             throw e;  
  22.         }//目标方法后的逻辑  
  23.                 System.out.println("end.....");  
  24.         return ret;  
  25.     }  
  26. }  



然后客户端代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.lang.reflect.Proxy;  
  2. public class Client {  
  3.     public static void main(String[] args) {  
  4.         //委托类对象  
  5.         Moveable mgr = new Tank();   
  6.         //处理类对象  
  7.         LogHandler h = new LogHandler(mgr);  
  8.         //代理类Proxy生一个实现了Moveable接口,并且包含新增了LogHandler处理逻辑的代理对象m  
  9.         Moveable m = Proxy.  
  10.             newProxyInstance(mgr.getClass().getClassLoader(), mgr.getClass().getInterfaces(), h);  
  11.         m.move();  
  12.     }  
  13. }  



这样加了日志逻辑的move方法运行结果如下:
start......
Tank Moving...
end......


下面我们来分析一下Proxy以及它的newProxyInstance(...)方法都干了些什么……
我借用尚学堂马士兵的Proxy代码来说明这个问题。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. import java.io.File;  
  2. import java.io.FileWriter;  
  3. import java.lang.reflect.Constructor;  
  4. import java.lang.reflect.Method;  
  5. import java.net.URL;  
  6. import java.net.URLClassLoader;  
  7. import javax.tools.JavaCompiler;  
  8. import javax.tools.StandardJavaFileManager;  
  9. import javax.tools.ToolProvider;  
  10. import javax.tools.JavaCompiler.CompilationTask;  
  11.   
  12. public class Proxy {  
  13.     public static Object newProxyInstance(ClassLoader loader,Class infce, InvocationHandler h) throws Exception { //JDK6 Complier API, CGLib, ASM  
  14.         String methodStr = "";  
  15.         String rt = "\r\n";  
  16.         //利用反射,获得infce接口中方法,本例中就是获得Moveable接口的方法move  
  17.         Method[] methods = infce.getMethods();  
  18.         //拼接infce中所有方法字符串,用来重写infce中的方法,本例中拼出来就是重写的move方法  
  19.         for(Method m : methods) {  
  20.             methodStr += "@Override" + rt +   
  21.                          "public void " + m.getName() + "() {" + rt +  
  22.                          "    try {" + rt +  
  23.                          "    Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +  
  24.                         /*方法最核心的代码,h是构造$Proxy1时传入的handler,本例中就是LogHandler对象,new Object[] { null}是move方法需要的参数,本例不需要,故为空。这一步将会使我们编写的处理类逻辑LogHandler的invoke方法得到调用。从而达到我们最初要在move方法前加日志逻辑的的目的,下面要做的就是把我们拼好的字符串生成类并load到内存就可以了这样就实现了动态生成代理类并加自己想加的逻辑*/  
  25.                          "    h.invoke(this, md,new Object[] { null});" + rt +  
  26.                          "    }catch(Exception e) {e.printStackTrace();}" + rt +  
  27.                           
  28.                          "}";  
  29.         }  
  30.           
  31.         String src =   
  32.             "package com.bjsxt.proxy;" +  rt +  
  33.             "import java.lang.reflect.Method;" + rt +  
  34.             //这里动态实现infce接口,本例中就是Moveable,构造方法中让Proxy持有处理类Handler的引用  
  35.             "public class $Proxy1 implements " + infce.getName() + "{" + rt +  
  36.             "    public $Proxy1(InvocationHandler h) {" + rt +  
  37.             "        this.h = h;" + rt +  
  38.             "    }" + rt +  
  39.               
  40.               
  41.             "    com.bjsxt.proxy.InvocationHandler h;" + rt +  
  42.             //这里是需要重写Moveable中的方法,见上面该字符串的拼接过程                
  43.             methodStr +  
  44.             "}";  
  45.         String fileName =   
  46.             "d:/src/com/bjsxt/proxy/$Proxy1.java";  
  47.         File f = new File(fileName);  
  48.         FileWriter fw = new FileWriter(f);  
  49.         fw.write(src);  
  50.         fw.flush();  
  51.         fw.close();  
  52.           
  53.         //compile编译上面拼好的字符串  
  54.         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
  55.         StandardJavaFileManager fileMgr = compiler.getStandardFileManager(nullnullnull);  
  56.         Iterable units = fileMgr.getJavaFileObjects(fileName);  
  57.         CompilationTask t = compiler.getTask(null, fileMgr, nullnullnull, units);  
  58.         t.call();  
  59.         fileMgr.close();  
  60.           
  61.         //load into memory and create an instance加载进内存并创建对象  
  62.         URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};  
  63.         URLClassLoader ul = new URLClassLoader(urls);  
  64.         Class c = ul.loadClass("com.bjsxt.proxy.$Proxy1");  
  65.         System.out.println(c);  
  66.           
  67.         Constructor ctr = c.getConstructor(InvocationHandler.class);  
  68.         Object m = ctr.newInstance(h);  
  69.           
  70.         //利用反射,这里就返回一个实现了infce接口也就是本例中Moveable接口的代理对像$Proxy1  
  71.         return m;  
  72.     }  
  73. }  

需要注意的是这里的Proxy类和JDK中java.lang.reflect.Proxy并不相同。只是他们的原理大致相同,实现的细节并不同。JDK中生成$Proxy1并不是拼字符串,而是直接生成二进制码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值