【Java设计模式】设计模式之 代理模式

来源:互联网 发布:linux ping 测试 编辑:程序博客网 时间:2024/06/11 03:34

设计模式之 代理模式

 

定义:为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。

 

代理模式的分类

 

虚拟代理

远程代理

保护代理

智能引用代理

 

智能引用代理

 

静态代理:代理对象和被代理对象在代理之前都是确定的。他们都实现相同的接口或者继承相同的抽象类。

 

有两种实现方式。

1.      通过继承实现。

2.      通过聚合实现。

 

情景案例:

我们有一个车类,车具有行驶的方法。通过代理,增加记录行驶时间的方法。

1.定义接口 Moveable.java

/* * 模拟行驶的接口 */public interface Moveable {void move();}
2.创建一个车类 Car.java 实现这个接口。
public class Car implements Moveable {public void move() {//实现开车try {Thread.sleep(new Random().nextInt(1000));System.out.println("汽车行驶中");} catch (InterruptedException e) {e.printStackTrace();}}}

3.现在我们为车类增加一个记录行驶时间的处理,创建一个代理类

用继承的方式实现。

public class Car2 extends Car {public void move() {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");super.move();//调用父类的方法,这就实现了Car2 对Car的代理long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");}}
用聚合的方式实现

/* * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象 */public class Car3 implements Moveable {public Car3(Car car){super();this.car = car;}private Car car;public void move() {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");car.move();//使用聚合的方式,把参数传进来进行调用long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");}}

继承和聚合实现静态代理,那个更合适?

如果我们要在记录行驶时间前增加记录日志功能,则需要创建Car4.java 继承Car2 或者继承Car,如果在增加其他的功能,则代理类就会无限的膨胀,越来越多。所以使用聚合的方式更适合用来实现静态代理。

使用聚合的方式实现静态代理

1.创建日志记录代理类

/* * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象 * 汽车日志的代理类 */public class CarLogProxy implements Moveable {public CarLogProxy(Moveable moveable){super();this.moveable = moveable;}private Moveable moveable;public void move() {System.out.println("日志开始");moveable.move();//使用聚合的方式,把参数传进来进行调用System.out.println("日志结束");}}
2.创建行驶时间代理类
/* * 使用聚合的方式实现静态代理,聚合就是在一个类中使用到另一个类的对象 */public class CarTimeProxy implements Moveable {public CarTimeProxy(Moveable moveable){super();this.moveable = moveable;}private Moveable moveable;public void move() {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");moveable.move();//使用聚合的方式,把参数传进来进行调用long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");}}
3.测试类,先记录日志,再记录行驶的时间。
/* * 测试类 */public class CTest {public static void main(String[] args) {Car car = new Car();CarTimeProxy ctp = new CarTimeProxy(car);CarLogProxy clp = new CarLogProxy(ctp);clp.move();}}

4.输出结果

我们可以自由组合这种方式,如先记录时间再记录日志。

public class CTest {public static void main(String[] args) {Car car = new Car();CarLogProxy clp = new CarLogProxy(car);CarTimeProxy ctp = new CarTimeProxy(clp);ctp.move();}}

思考:如果还要为火车、自行车实现代理类,那么还要再按照上面的方式再分别实现两个类。有没有办法可以让一个类代理火车、自行车、汽车呢?

 

动态产生代理,实现对不同类,不同方法的代理。

所谓Dynamic Proxy 是这样一种class:

它是在运行时生成的class

该class需要实现一组interface

使用动态代理类时,必须实现InvocationHandler接口

JDK动态代理

Java 动态代理类位于java.lang.reflect包下,一般主要涉及以下两个类:

①  Interface InvocationHandler:该接口中仅定义了一个方法

public object invoke(Objectobj,Method method,Object[] args)

在实际使用时,第一个参数obj表示最终生成的代理类对象,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。

②  Proxy: 该类即为动态代理类

static ObjectnewProxyInstance(ClassLoader loader,Calss[] interface, InvocationHandler h): 返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)

 

JDK动态代理实现步骤

1.      创建一个实现接口InvocationHandler的类,它必须实现invoke方法。

2.      创建被代理的类以及接口。

3.      调用Proxy的静态方法,创建一个代理类

newProxyInstance(ClassLoaderloader,Class[] interfaces,InvocationHandler h)

4.      通过代理调用方法。

 

1创建TimeHandler.java 实现InvocationHandler 接口

public class TimeHandler implements InvocationHandler {public TimeHandler(Object target){super();this.target = target;}private Object target;/* * 参数: * proxy    代理类的对象 * method  被代理对象的方法 * args     方法的参数 *  * 返回值: * Object 方法的返回值 */public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");method.invoke(target);long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");return null;}}

模拟JDK动态代理实现思路

 

动态代理实现思路

实现功能:通过Proxy 的 newProxyInstance返回代理对象。

1.      声明一段源码(动态产生代理)

2.      编译源码(JDK Compiler API),产生新的类(代理类)

3.      将这个类load到内存当中,产生一个新的对象(代理对象)

4.      return 代理对象。

 

初步实现:首先我们自己创建一个Proxy类,并创建newProxyInstance方法

public class Proxy {/* * 创建一个方法用来返回我们的代理对象 */public static Object newProxyInstance(Class infce) throws Exception{String rt = "\r\n";//windows下的换行符/* * 第一步:声明一段源码 * 替换interface的名字 *///获取我们的方法String methodStr ="";for(Method m : infce.getMethods()){methodStr +="public void "+m.getName()+"() {"+rt+"long startTime = System.currentTimeMillis();"+rt+"System.out.println(\"汽车开始行驶\");"+rt+"moveable."+m.getName()+"();//使用聚合的方式,把参数传进来进行调用"+rt+"long endTime = System.currentTimeMillis();"+rt+"System.out.println(\"汽车结束行驶,行驶时间:\"+(endTime - startTime)+\"毫秒\");"+rt+"}";}String str="package com.meng.proxy;"+rt+"public class $Proxy0 implements "+infce.getName()+"{"+rt+"public $Proxy0("+infce.getName()+" moveable){"+rt+"super();"+rt+"this.moveable = moveable;"+rt+"}"+rt+"private "+infce.getName()+" moveable;"+rt+ methodStr+rt+"}";/* * 第二步:生成一个文件 */String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";File file = new File(filename);//调用commons.io.jar 的方法写文件FileUtils.write(file, str);/* * 第三步:编译生成的java类 *//* * 这里需要把jre 改为jdk *///得到当前系统的编译器JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//文件管理者StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);//根据文件名得到文件的数据Iterable units = fileManager.getJavaFileObjects(filename);//编译任务CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);//进行编译t.call();fileManager.close();/* * 第四步:把编译好的文件load到内存 */ClassLoader cl = ClassLoader.getSystemClassLoader();Class c = cl.loadClass("com.meng.proxy.$Proxy0");Constructor ctr = c.getConstructor(infce);        //根据构造方法把Car传进去return ctr.newInstance(new Car());}}

分欣上面这段代码,我们传递了一个接口拼接了一段代理类的源码$Proxy0

把这段源码写入$Proxy0.java 文件并编译这个文件,并把编译好的文件加载到内存,获取构造方法并创建代理类的对象,返回这个代理类对象。

bin目录下生成的文件


拼接源码文件的内容:
package com.meng.proxy;public class $Proxy0 implements com.meng.proxy.Moveable{public $Proxy0(com.meng.proxy.Moveable moveable){super();this.moveable = moveable;}private com.meng.proxy.Moveable moveable;public void move() {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");moveable.move();//使用聚合的方式,把参数传进来进行调用long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");}}
测试类

public class CTest {public static void main(String[] args) throws Exception {Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class);m.move();}}

运行结果

分析上面的实现方法,我们发现增加业务逻辑的实现写死在了代码里,而且构造函数那传入的是new Car() 这样就不具有通用性,下面对这种方法进行扩展改进,进行解耦

 

改进实现:

创建InvocationHandler 接口

public interface InvocationHandler {    //第一个参数表示代理类对象,第二个参数表示方法对象,省略方法参数public void invoke(Object o,Method m);}
创建TimeHandler 实现这个接口,在这里增加我们业务处理逻辑

public class TimeHandler implements InvocationHandler {//被代理的对象private Object target;public TimeHandler(Object object){super();this.target = object;}@Overridepublic void invoke(Object o, Method m) {try {long startTime = System.currentTimeMillis();System.out.println("汽车开始行驶");m.invoke(target);long endTime = System.currentTimeMillis();System.out.println("汽车结束行驶,行驶时间:"+(endTime - startTime)+"毫秒");} catch (Exception e) {e.printStackTrace();} }}
改写我们的 Proxy.java

public class Proxy {/* * 创建一个方法用来返回我们的代理对象 */public static Object newProxyInstance(Class infce,InvocationHandler h) throws Exception{String rt = "\r\n";//windows下的换行符/* * 第一步:声明一段源码 * 替换interface的名字 *///获取我们的方法String methodStr ="";for(Method m : infce.getMethods()){methodStr +="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 str="package com.meng.proxy;"+rt+"import com.meng.proxy.InvocationHandler;"+rt+"import java.lang.reflect.Method;"+rt+"public class $Proxy0 implements "+infce.getName()+"{"+rt+"public $Proxy0(InvocationHandler h){"+rt+"super();"+rt+"this.h = h;"+rt+"}"+rt+"private InvocationHandler h;"+rt+ methodStr+rt+"}";/* * 第二步:生成一个文件 */String filename = System.getProperty("user.dir")+"/bin/com/meng/proxy/$Proxy0.java";File file = new File(filename);//调用commons.io.jar 的方法写文件FileUtils.write(file, str);/* * 第三步:编译生成的java类 *//* * 这里需要把jre 改为jdk *///得到当前系统的编译器JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();//文件管理者StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);//根据文件名得到文件的数据Iterable units = fileManager.getJavaFileObjects(filename);//编译任务CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);//进行编译t.call();fileManager.close();/* * 第四步:把编译好的文件load到内存 */ClassLoader cl = ClassLoader.getSystemClassLoader();Class c = cl.loadClass("com.meng.proxy.$Proxy0");Constructor ctr = c.getConstructor(InvocationHandler.class);return ctr.newInstance(h);}}
重新生成的代理类 $Proxy0.java 如下:

public class $Proxy0 implements com.meng.proxy.Moveable{public $Proxy0(InvocationHandler h){super();this.h = h;}private InvocationHandler h;public void move() {try{Method md = com.meng.proxy.Moveable.class.getMethod("move");h.invoke(this,md);}catch(Exception e){e.printStackTrace();}}}
测试类:

public class CTest {public static void main(String[] args) throws Exception {Car car = new Car();InvocationHandler h = new TimeHandler(car);Moveable m = (Moveable)Proxy.newProxyInstance(Moveable.class,h);m.move();}}

运行结果:

这样我们的动态代理就具有了扩展性

理解的还不是很清楚,如有疑问请移步

慕课网地址:http://www.imooc.com/learn/214













0 0
原创粉丝点击