摘要:本篇笔记是对Java中很难理解的设计模式——代理模式的学习记录、包括由问题引出代理模式的出现、包括静态代理、和核心的动态代理。至于为什么从最难理解的开始、则是想给自己传达个信念:万事开头难!那就从最难理解的开始!
主要内容:
1、由问题引出设计模式
2、静态代理的产生与实现
3、继承与聚合哪个好
4、动态代理的产生与实现
5、总结与补充
实际开发中、我们常常要对某个方法、类、功能进行一些处理、比如最常见的记录日志(当我们执行某个方法之前记录一下日志、当方法结束之后又记录一下日志)、权限校验(当符合某些条件的时候才去执行此方法)、事务控制(当我们调用要与数据库打交道的方法的时候、在方法执行之前开启事务、方法执行之后提交或者回滚事务)等等这些方面的处理。而这些类有的是直接被打成jar包、根本就看不到源码、别说修改了。那我们怎么办?问题就来了:如何在不修改一个类的代码的情况下去实现我们想要的上述的功能?比如我就想知道某个jar包中的类的某个方法执行的时间是多少!
为解决上面的问题、我们最可能、也最直接能想到的就是:
1、自己写一个类来继承测试方法所在的类。
2、在main方法中调用父类的被测试方法之前记录当前系统时间作为起始时间。
3、调用被测试方法、调用完之后记录结束时间、并输出时间差、这就是要测试的方法的执行时间。下面Java模仿实现代码:
a) 假设Bird是我们看不到源码的类、他其中有个方法flying就是我们要测试的方法——Bird代码:
package com.chy.proxy; import java.util.Random; public class Bird{ public void fly(){ System.out.println("bird is flying..."); try { //停顿的时间长一点、方便我们去观察 Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
b) 我们自己定义个BirdTimeProxy类、继承Bird类、那么我们自己定义的类就拥有了使用Bird类的flying方法、那就可以实现测试执行时间。BirdTimeProxy——代码:
package com.chy.proxy; public class BirdTimeProxy extends Bird { //持有一个FlyAble的引用、即被代理的类的引用 private FlyAble flyObject; //通过构造方法实例化FlyAble public BirdTimeProxy(FlyAble flyObject) { super(); this.flyObject = flyObject; } public void fly() { //obtain start time long start = System.currentTimeMillis(); System.out.println("start time:" + start); //invoke bird‘s fly method flyObject.fly(); //obtain end time long end = System.currentTimeMillis(); System.out.println("time:" + (end -start)); } }
4、测试结果:
start time:1394507403734 bird is flying... time:31
上面完美的解决了对一个类的一个方法的一种处理方式、看起来我们的BirdTimeProxy是不是有点代理的样子了?但是明显上面有很大的局限性、甚至可以说是缺陷!那就是没有用到我们常常挂在嘴边的抽象、多态。当然这么讲可能觉得不理解、没有直观的印象。问题:当我想换一个会飞的对象(比如飞机)来测试一下他飞行的方法的执行时间的时候怎么办?是不是只能重新写一个我们自己的类来继承飞机类、然后重复上边的步骤?很显然、这样的做法没有任何我们觉得可取的优点。只会将时间浪费在重复的coding代码中。
对与会飞的东西、我们第一想法应该是可以抽象出他们共同的特性——会飞、那么我们可不可以定义一个FlyAble接口、提供一个fly方法呢、这样想具有飞的功能就实现这个接口就ok了。
到了上面一步、距离我们想要的结构就更近一步了、那就是使用聚合的形式将被代理类与代理类相结合!这样我们在一个代理类中就可以对任意实现了我们规定的接口的实现类进行我们想要的处理、可能这样讲有点迷惑、直接通过代码来、在上面的代码基础上修改即可:
1、添加一个FlayAble接口、里面就一个fly方法——FlyAble代码:
package com.chy.proxy; public interface FlyAble { public void fly(); }
2、让想要具有飞的功能的类都实现个这个接口。Bird代码:Aircraft代码:
package com.chy.proxy; import java.util.Random; public class Bird implements FlyAble{ @Override public void fly(){ System.out.println("bird is flying..."); try { //停顿的时间长一点、方便我们去观察 Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } package com.chy.proxy; import java.util.Random; public class Aircraft implements FlyAble{ @Override public void fly() { System.out.println("aircraft is flying..."); try { //停顿的时间长一点、方便我们去观察 Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
3、此时的代理类——FlyAbleTimeProxy(此时再叫BirdTimeProxy就不合适了、因为他现在是所有能飞的东西的时间代理类)代码:
package com.chy.proxy; public class FlyAbleTimeProxy{ //持有一个FlyAble的引用、即被代理的类的引用 private FlyAble flyObject; //通过构造方法实例化FlyAble public FlyAbleTimeProxy(FlyAble flyObject) { super(); this.flyObject = flyObject; } public void fly() { //obtain start time long start = System.currentTimeMillis(); System.out.println("start time:" + start); //invoke bird‘s fly method flyObject.fly(); //obtain end time long end = System.currentTimeMillis(); System.out.println("time:" + (end -start)); } }
4、为了正规点我们另起一个Client测试代理类:
package com.chy.proxy; public class Client { public static void main(String[] args) { proxyByOne(); } /** * 分别使用时间代理、日志代理 */ private static void proxyByOne() { //获取代理类、传入想被代理的FlayAble具体对象 FlyAbleTimeProxy ftp = new FlyAbleTimeProxy(new Aircraft()); ftp.fly(); } }
5、测试结果:
start time:1394510295500 aircraft is flying... time:281
说明:因为使用了Random函数、所以每次sleep的时间都不同、但是可以通过xxxis flying 来区别被代理的类。
上面的代码就显得有点能看了、最起码我们在里面能找到多态、接口这些东西的使用、同时也解决了一个代理可以代理具有同一特性的类(这里就是多态的强大与灵活)、但是设计是无止境的!一个问题的解决往往伴随这新的问题的出现:如果我想要对fly方法进行日志记录、怎么办?你脑海中的第一反应可能会是再写一个类FlayAbleLogProxy、没错!这本身就是一种解决办法、更好的解决方法下面会有、这里不是重点、重点是——我既想记录时间、又想记录日志、怎么办?写第三个代理类:FlayAbleLogAndTimeProxy。好吧、继续来问题:我想先记录时间、在记录日志?之后又想先记录日志再记录时间?之后我觉得还应该添加一个事务控制、然后他们三个的顺序我还想换换?然后我觉得还应该加个权限控制。。。。。。不要觉得我犯贱、想这想那。。。问题真的来临的时候你怎么解决?难道每出现一个新的变动就要写一个新的代理类?那要写多少?什么时候是个头?想解决问题、还是得找Java的多态、抽象。四个字说起来简单、真真正正的用的时候你才会体会到他的博大精深!解决方法:
我们有没有想过代理类也是类?也可以被其他的代理类所代理?条件无非就是和原始的被代理类实现同一个接口!然后可以根据不同的需求顺序来调整他们的代理顺序?比如我先使用记录时间的类来代理被代理类、然后使用记录日志的代理类来代理记录时间的代理类?这样是不是实现了先记录时间后记录日志的代理?如果有多个、我们完全可以按照这种方式去实现!还是以代码来说明:
1、 新添加一个能够记录日志的代理类(想来没有任何难度、注意实现了FlyAble接口)——FlyAbleLogProxy代码:
package com.chy.proxy; public class FlyAbleLogProxy implements FlyAble { //持有一个FlyAble的引用、即被代理的类的引用 private FlyAble flyObject; //通过构造方法实例化FlyAble public FlyAbleLogProxy(FlyAble flyObject) { super(); this.flyObject = flyObject; } @Override public void fly() { System.out.println("log start..."); flyObject.fly(); System.out.println("log end..."); } }
2、 同时FlyAbleTimeProxy也要实现FlyAble接口:
public class FlyAbleTimeProxy implements FlyAble {...}
3、 这样我就可以为我们要代理的类添加任意顺序个数的代理——Client代码:
package com.chy.proxy; public class Client { public static void main(String[] args) { proxyByOne(); System.out.println("==============================="); proxyLogThanTime(); System.out.println("==============================="); proxyTimeThanLog(); } /** * 分别使用时间代理、日志代理 */ private static void proxyByOne() { //获取代理类、传入想被代理的FlayAble具体对象 FlyAbleTimeProxy ftp = new FlyAbleTimeProxy(new Aircraft()); FlyAbleLogProxy flp = new FlyAbleLogProxy(new Aircraft()); ftp.fly(); flp.fly(); } /** * 先使用日志、再使用时间 */ private static void proxyLogThanTime(){ FlyAbleLogProxy flp = new FlyAbleLogProxy(new Aircraft()); FlyAbleTimeProxy ftp = new FlyAbleTimeProxy(flp); ftp.fly(); } /** * 先使用时间、再使用日志 */ private static void proxyTimeThanLog(){ FlyAbleTimeProxy ftp = new FlyAbleTimeProxy(new Aircraft()); FlyAbleLogProxy flp = new FlyAbleLogProxy(ftp); flp.fly(); } }
结果:
start time:1394510408890 aircraft is flying... time:266 log start... aircraft is flying... log end... =============================== start time:1394510409718 log start... aircraft is flying... log end... time:360 =============================== log start... start time:1394510410078 aircraft is flying... time:922 log end...
留心的可以发现、上面的静态代理是一步步从类的继承走向聚合的。首先是通过继承来实现单一类的代理、这样的代理、一个代理类只能代理一个类或者其父类。如果想代理别的类、或者对同一类实现不同的代理、那就要另造代理类、如果需要代理的类特别多、则随之衍生的代理类则无限的膨胀下去、简称——类爆炸。这样的话我们基本看不到他们的可取之处。设计的不合理可以定位与对Java多态的没有充分利用。
当我们使用聚合的时候、发现会灵活的太多、最起码进一步解决了继承所带来的类爆炸的问题(当然没有完全解决、对于不同的功能还是要我们去实现不同的代理类)、代理拥有被代理类的父类引用、这样代理可以代理代理类、这种随意的组合无疑让我们方便很多!这是继承所不能带给我们的优势。同时从这里可以看出一点:从接口出发的优势!所以我们在用许多框架的时候、他会强制要求我们提供接口、然后再提供他的实现类、就是为了更强的灵活性和可扩展性!说到底这就是一种抽象、多态的应用!
通过静态处理之后、我们发现现实与理想更进一步了。最起码上面的看起来并不是需要写那么多的类了。但是还是要避免不了的去写不同的类的代理、不同功能的代理。那么有没有可能使用一个类来完成上面的所有功能的呢?
动态代理!动态代理可以对任意的对象、任意的接口的方法、实现任意的代理!我们不必关心、也关心不了代理内部的实现、只要按照代理类的要求来使用他、就能实现上面的功能。当然、孤木不成林、我们需要按照他的要求来实现具有自己指定功能的类、以及被代理类。
a)既然我们的目标是对任意对象、任意接口的方法、实现任意的代理、显然要使用的是一个高度抽象的接口、或者类来构建骨架。然后在使用时传入具体的实现类的对象。
b)开始的模型从简单的开始、就为一个Bird生成一个动态代理。保留Bird类和FlyAble接口、接下来就围绕如何使用动态代理来生成Bird的代理类。
c)既然是需要有任意性、那么就先定义一个所有代理类必须实现的接口InvocationHandler、使得代理类具有任意性、并且声明一个方法invoke(Object o, Method m)、用于调用实现类中的此方法来实现代理。InvocationHandler接口:
package com.chy.proxy; import java.lang.reflect.Method; public interface InvocationHandler { /** * 此方法是在Proxy中被真正执行的。Proxy会在后面有、它是核心。 * @param o 具体的代理类对象、 * @param m 被执行的方法对象、 */ public void invoke(Object o , Method m); }
d)InvocationHandler的一个实现子类:TimeHandler(它不再是某一具体类的时间代理、而是Object):
package com.chy.proxy; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler { //被代理对象 private Object target; public TimeHandler(Object target) { super(); this.target = target; } /** * 细心的可以发现第一个参数Object、并没有使用。 * 只是在这里没有使用、我们可以按照自己的需求、、 * 传入一个需要借助的Object来实现特定的功能。 * 此方法会在Proxy中生成的代理类$Proxy1被真正的执行、Object会传this、也就是$Proxy1! */ @Override public void invoke(Object o, Method m) { long start = System.currentTimeMillis(); System.out.println("starttime:" + start); System.out.println(o.getClass().getName()); try { //真正执行被代理对象的被代理方法。 m.invoke(target); } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("time:" + (end-start)); } }
e)接下来就是核心的Proxy!Proxy只有一个功能——为我们产生代理类!当然需要条件、为谁产生代理类(包括他的所有方法)?产生什么样的代理类?这个类当我们完成之后、就不必再修改。就是因为他的任意性!当然这里面使用的点反射的东西、但是也没有多少、看懂不难。
f)主要实现思路:
i、将所有方法代码拼接成字符串、
ii、 将生成代理类的代码拼接成字符串(包含所有方法拼接成的字符串)、iii、将此字符串写入文件中、使用JavaComplier对齐进行编译、
v、load进内存供我们使用。返回代理实例。
Proxy代码:
package com.chy.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; /** * 为所有的类生成指定的代理类。 * 简单起见仅仅考虑没有返回值没有参数的方法的代理。 * @author Administrator * */ @SuppressWarnings("unchecked") public class Proxy { public static Object newInstance(Class infce, InvocationHandler h) throws Exception{ String methodStr = ""; String rt = "\r\n"; 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 + "}"; } String src = "package com.chy .proxy;" + rt + "import java.lang.reflect.Method;" + rt + "public class $Proxy1 implements " + infce.getName() + "{" + rt + " com.chy.proxy.InvocationHandler h;" +rt + " public $Proxy1(InvocationHandler h){" +rt + " this.h = h;" + rt + " }" + rt + " "+methodStr + "}"; String fileName = "d:/src/com/chy/proxy/$Proxy1.java"; File f = new File(fileName); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //compile the proxy class 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:/" + "d:/src/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("com.chy.proxy.$Proxy1"); Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); return m; } }
1、总结
代理到这里就告一段落了、从问题的产生、到一步步的解决、优化。从继承式的静态代理到聚合式的静态代理、从静态代理到动态代理、逐渐的揭示了代理的功能与实现方式。当一个代理类被创建好之后、我们就不必再关心他的信息、包括如何实现、具体在哪里等等、所要知道的就是如何使用即可。
我们完全可以通过配置文件来指定我们想要使用的代理类。这点是不是让你想到了spring的AOP?没错、spring的AOP是动态代理的一种应用、而不是动态代理是AOP的一种应用、别弄混了。
还有点题外话、spring控制的事务是在什么时候开启的?是在调用Dao层开启的还是在调用Service层还是Action层?答案:一个Service层有可能调用多个Dao、所以是在调用Service层方法开始、方法执行完之后结束、根据结果来判断是提交还是回滚。
2、补充
补充两个项目结构图:静态代理和动态代理分开的。
静态代理: 动态代理:
原文:http://blog.csdn.net/crave_shy/article/details/21000887