Proxy代理模式
来源:互联网 发布:java面向对象设计原则 编辑:程序博客网 时间:2024/06/03 17:55
当别人给你了源包,当你是用该源包方法的时候,无法修改源代码(比如只给你一个编译过的class文件)来修改实现你所需要的功能,或者需要把业务代码和日志打印完全分离开,不想要业务代码里面混着日志代码,或者需要提供的java类什么地方是瓶颈,肯定需要在方法的前后加上一些代码,就需要用到代理模式。
Tank
package Proxy;import java.util.Random;public class Tank implements Moveable{ @Override public void move() { System.out.println("Tanking moving...."); try { Thread.sleep(new Random().nextInt(10000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
Tank的代理者们
//日志代理package Proxy;//和被代理对象实现一样的接口public class TankLogProxy implements Moveable{ Tank tank; public TankLogProxy(Tank tank) { super(); this.tank = tank; } @Override public void move() { //方法开始执行之前,可以进行检查、记录等操作 System.out.print("Tank..start"); tank.move();//--------------代理调用 //方法结束后可以写日志等。 System.out.print("Tank..stop"); }}
//时间代理package Proxy;import java.sql.Time;//和被代理对象实现一样的接口public class TankTimeProxy implements Moveable{ Tank tank; public TankTimeProxy(Tank tank) { super(); this.tank = tank; } @Override public void move() { //方法开始执行之前,可以进行检查、记录等操作 long start=System.currentTimeMillis(); tank.move();//--------------代理调用 //方法结束后可以写日志等。 long end=System.currentTimeMillis(); System.out.println(start-end); }}
调用
public class Client { public static void main(String[] args) { Tank tank=new Tank(); TankLogProxy tlp=new TankLogProxy(tank);//把tank对象交给其代理对象 tlp.move(); }}
两个代理一起使用(时间代理包着日志代理)
package Proxy;public class Client { public static void main(String[] args) { Tank tank=new Tank(); TankLogProxy tlp=new TankLogProxy(tank);//把tank对象交给其代理对象 TankTimeProxy ttp=new TankTimeProxy(tlp);//以为都实现Moveable接口,把tlp日志代理对象交给时间代理对象 ttp.move(); //先调用时间日志的move,会执行获取时间,因为把tlp传给时间,所以move方法执行的是日志代理的move }}
聚合的方式实现代理:
1、会和原来被代理对象实现一样的接口,代理者内部会聚合一个被代理对象。
2、所以,代理者里面可以装任何实现实现了某接口的对象,甚至是另外一个代理者。
问题:
1、当既需要时间代码,也需要日志代理,可以把它们组合在一起用,弄成一个新的代理者(各种不同的功能叠加,代理一个类就会包一层,再把该代理类代理,又会继续包一层,会使得继承或者聚合无限制的往下走,继承会产生类爆炸,聚合会产生好多层包下去),那如果还需要权限验证或者先执行日志后执行时间代理呢?
2、若Moveable接口里面增加一个方法,该方法也需要日志代理和时间代理,是不是还要在该方法内重写一遍打印时间和记录日志的操作呢?重写一个代理者?
3、若需要对任意一个对象进行时间代理和日志代理,而不仅仅只是Tank对象,比如,我想对Car实现,对Animal实现,难道还要全部写一遍各自的Proxy吗?比如如果需要对系统中任意对象进行运行时间的观察,那么又该怎样呢?
产生一个代理,可以对任何一个对象进行代理
以上代码中,代理者代理的对象都是实现了Moveable接口的对象。
所以,代理模式代理的对象都实现了某一个接口,所以真正代理的时候,是根据那个接口来实现代理的,而不是类。
Spring中也是如此要求的,但是也可以不用接口实现代理,也可以用继承,但是Spring是不推荐这样做的。
AOP是动态代理的一种应用。
动态代理
模拟JDK实现动态代理
Moveable作为需要实现动态代理的接口
package Proxy;public interface Moveable { void move(); void stop();}
编写Proxy类,用来作为产生代理对象的类(代理的总代理)
package Proxy;public class Proxy { /** *所有被代理对象都由该方法产生代理对象 * @return */ public static Object newProxyInstance(){ String src="package Proxy;" + "import java.sql.Time;" + "public class TankTimeProxy implements Moveable{" + "Moveable tank;" + " public TankTimeProxy(Moveable tlp) {" + " super();" + "this.tank = tlp;" + "}" + " @Override" + "public void move() {" + "//方法开始执行之前,可以进行检查、记录等操作" + " long start=System.currentTimeMillis();" + " tank.move();" + "//方法结束后可以写日志等。" + "long end=System.currentTimeMillis();" + " System.out.println(start-end);" + "}" + " @Override" + "public void stop() {" + " //方法开始执行之前,可以进行检查、记录等操作" + " long start=System.currentTimeMillis();" + " tank.move();" + " //方法结束后可以写日志等。" + " long end=System.currentTimeMillis();" + " System.out.println(start-end);" + " }" + "}"; return null; }}
以上代码中,如果src代表的一段源代码可以被编译装入内存中,直接产生一个新的代理对象,那么项目中甚至可以不写TankTimeProxy 类,直接可以由Proxy类的newProxyInstance方法根据src动态的生成代理对象。
动态代理的思想:不需要再写什么TankTimeProxy,只需要调用Proxy的newProxyInstance方法就可以直接返回代理对象,也不需要什么代理对象的名字。
如何生成动态代理对象呢?要能够动态编译src的这段源代码,这样就可以在运行的时候想得到什么类就得到什么类。
动态代理就是为了解决业务层分级、解耦、类太多的问题。
动态编译:
1、JDK6 Complier API,用jdk本身就是用接口实现动态代理
2、网上现有的开源的东西,如CGLib、ASM(甚至不用源码编译,会直接生成二进制文件,因为java的二进制文件的格式也是公开的。如果能理解二进制文件的意思,甚至连编译器都不需要了。)
【Spring中就是用的CGLib、ASM】
注意:编译器API只有在JDK6中有,以前的没有
JDK6调用Complier API动态编译
package main;import java.io.File;import java.io.FileWriter;import java.io.IOException;import javax.tools.JavaCompiler;import javax.tools.ToolProvider;public class main { public static void main(String[] args) throws IOException { String src="package main;" +"\r\n"+ "import java.sql.Time;" +"\r\n"+ "import Proxy.Moveable;"+"\r\n"+ "public class TankTimeProxy implements Moveable{" +"\r\n"+ "Moveable tank;" +"\r\n"+ " public TankTimeProxy(Moveable tlp) {" +"\r\n"+ " super();" +"\r\n"+ " this.tank = tlp;" +"\r\n"+ "}" +"\r\n"+ " @Override" +"\r\n"+ " public void move() {" +"\r\n"+ " //方法开始执行之前,可以进行检查、记录等操作" +"\r\n"+ " long start=System.currentTimeMillis();" +"\r\n"+ " tank.move();" +"\r\n"+ " //方法结束后可以写日志等。" +"\r\n"+ " long end=System.currentTimeMillis();" +"\r\n"+ " System.out.println(start-end);" +"\r\n"+ "}" +"\r\n"+ " @Override" +"\r\n"+ " public void stop() {" +"\r\n"+ " //方法开始执行之前,可以进行检查、记录等操作" +"\r\n"+ " long start=System.currentTimeMillis();" +"\r\n"+ " tank.move();" +"\r\n"+ " //方法结束后可以写日志等。" +"\r\n"+ " long end=System.currentTimeMillis();" +"\r\n"+ " System.out.println(start-end);" +"\r\n"+ " }" +"\r\n"+ "}"+"\r\n"; //先把源代码写入一个临时文件中 String fileName=System.getProperty("user.dir");//获取当前文件的系统目录,就是项目的根路径 String temp=fileName+"/src/main/TankTimeProxy.java";//临时文件系统路径 File f=new File(temp); FileWriter fw=new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //动态编译 JavaCompiler complier=ToolProvider.getSystemJavaCompiler();//拿到编译器对象 StandardJavaFileManager fileMgr=complier.getStandardFileManager(null,null,null);//文件管理对象 Iterable units=fileMgr.getJavaFileObjects(temp);//得到需要编译的文件 CompilationTask t=complier.getTask(null,fileMgr,null,null,null,units);//一次编译任务 t.call();//调用编译任务 fileMgr.close();//关闭 //把class类文件load到内存中(用ClassLoader把class文件加载到内存,该class文件必须是在classPath下的,而自己写的动态 编译的class文件是在源码src下而不在bin里, //所以load class不能用ClassLoader,要用URLClassLoader。如果把自己动态编译的class放到bin里,就会和编译器自动编译的混淆在一起) URL[] urls=new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src")};//在找class的时候去该目录下找 URLClassLoader ul=new URLClassLoader(urls); Class c=ul.loadClass("main.TankTimeProxy");//网上传来的class文件也可以这样load进来 //生成新对象,用到反射 Constructor ctr=c.getConstructor(Moveable.class);//拿到构造方法,表示找到一个参数是Moveable.class的构造方法 Moveable m=(Moveable) ctr.newInstance(new Tank());//Tank为被代理的对象 m.move(); }}
我们根本不知道具体的是什么类的代理对象,只知道是实现了Moveable接口的类,这就是动态生成代理类对象。
Moveable m=(Moveable) ctr.newInstance(new Tank());//Tank为被代理的对象
可能遇到的问题:
JavaCompiler:JDK6中新提供的一个类
JavaCompiler complier=ToolProvider.getSystemJavaCompiler();
会拿到系统中默认的java编译器,其实就是javac。(如获取的时候报null,空指针异常,往往是由配置引起的,打开eclipse,在installed JRES中,jre是只包含纯运行环境,不包含编译的,要添加我们自己的jdk环境,而不是纯jre。) 在非常多Java应用中需要在程序中调用Java编译器来编译和运行。但在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这个jar的路径。而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。使用这个包,我们能不用将jar文件路径添加到classpath中了。
JDK6公开了Complier API,不用JDK6也可以进行动态编译,如使用编译命令(需要环境支持)或者使用Sun未公开的编译类。
- Proxy模式 代理模式
- 代理模式:Proxy模式
- 代理(Proxy)模式
- 代理模式Proxy Design
- 代理(Proxy)模式
- 代理(Proxy)模式
- 代理模式(Proxy Pattern)
- 代理(Proxy)模式
- 代理模式(Proxy Pattern)
- proxy(代理)模式
- Proxy Pattern (代理模式)
- Proxy pattern 代理模式
- 代理模式(Proxy)
- java 代理(proxy)模式
- Proxy 代理模式
- 代理模式(Proxy)
- 代理模式(Proxy pattern)
- Proxy(代理)模式
- logback配置和使用
- Oracle 从10.2.0.1升级到10.2.0.5
- virtualbox 磁盘扩容
- [Linux Project] Linux Kernel Module for Listing Tasks
- flask -- route修饰器源码
- Proxy代理模式
- 解决浏览器会自动填充用户名密码的问题
- 个性化学习之自适应测试[Adaptive Test]
- Python小结
- 一个简单的html测试tomcat本地服务器的问题处理一
- 输入检查
- android focus查找 方向键如何查找focus
- GYM 100488 A. Yet Another Goat in the Garden(计算几何)
- Maven下载Jar包同时下载source和javadoc