您的位置:首页 > 编程语言 > Java开发

设计模式之代理模式

2016-05-26 15:20 441 查看

1.代理模式简介(Proxy)

1.1代理模式定义

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

1.2代理模式的分类

  远程代理:

为不同的地理对象提供局域网代表对象

  虚拟代理:

根据需要将资源消耗很大的对象进行延迟加载,真正需要的时候再进行创建。(如访问网页时图片的加载,可先用别的图片进行代替)

  保护代理:

控制对一个对象的访问权限。(如论坛游客不能发帖等)

  智能引用代理:

提供对目标对象额外的服务。(如Spring AOP模式)

2.智能引用代理案例

只能引用代理是在实际实践中使用醉倒的一种代理。

智能引用代理可以分为静态代理和动态代理

2.1 静态代理

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

下面我们通过一个具体的案例来看一下。

假设有一辆车,我们要让他行驶,停下来,并记录它的行驶时间。可以用以下模式

2.1.1普通的实现形式

首先新建一个Movable接口

public interface Movable {
void move();
}


建立一个Car类继承Movable接口并实现move()方法

import java.util.Random;

public class Car implements Movable {

@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();// System.currentTimeMillis();获取系统当前时间
System.out.println("汽车开始行驶..........");

// 实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中、、、、、、、");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();

System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}

}


在测试方法中进行测试

public class Test {
public static void main(String[] args) {
Car car = new Car();
car.move();
}
}


测试结果为

汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:127毫秒


下面是一个静态代理的两种方式:

2.1.2继承方式实现代理

修改Car中的move()方法,使其只实现行驶功能,开始实行和行驶完成功能在子类中进行实现

import java.util.Random;

public class Car implements Movable {

@Override
public void move() {

// 实现开车
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("汽车行驶中、、、、、、、");
} catch (InterruptedException e) {
e.printStackTrace();
}

}

}


新建一个子类Car2继承Car并重写move()方法

/**
* 继承的方式代理Car
*
* @author Administrator
*
*/
public class Car2 extends Car {

@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");
super.move();
// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();

System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}

}


在测试方法中测试

public class Test {
public static void main(String[] args) {

Car car2 = new Car2();
car2.move();

}
}


这种方式为继承方式实现代理。

2.1.3 聚合的方式实现代理

新建一个car3继承movable接口,并实现move()方法。

在类中添加一个Car类型的全局变量,并添加带有Car的构造方法。

在move()方法中实现Car中的move方法。

/**
* 聚合的方式实现代理
*
* @author Administrator
*
*/
public class Car3 implements Movable {

private Car car;

public Car3(Car car) {
super();
this.car = car;
}

@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");

car.move();

// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();

System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}

}


在测试类中测试

public class Test {
public static void main(String[] args) {

Car car = new Car();
Movable car3 = new Car3(car);
car3.move();

}
}


这种就是通过聚合的方式实现代理。

2.1.4 继承方式和聚合方式的对比

如果希望实现类功能的叠加。先记录日志,再记录时间。

需要新建一个Car4继承Car,先实现日志的处理再计算运行时间。

如果希望先记录时间,再记录日志的处理,就需要新建一个Car5,先对时间处理,再对日志处理。

如果想实现先对权限的判断,再做日志的处理,再做时间的处理,只能新建一个Car6。

如果通过继承的方式对类进行代理,当功能增加或者改变时,只能通过增加类的方式进行。类只会无限膨胀下去。因此一般不推荐继承的方式代理。

以下为一个聚合代理方式,先记录日志再记录时间

新建一个日志代理类,实现movab接口

/**
* 对时间的代理
* @author Administrator
*
*/
public class CarTimeProxy implements Movable {

private Movable movable;//这次不是Car属性,而是他们共同的接口

public CarTimeProxy(Movable movable) {
super();
this.movable = movable;
}

@Override
public void move() {
// 记录一下汽车行驶的开始时间
long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");

movable.move();

// 记录一下汽车行驶的终止时间
long endtime = System.currentTimeMillis();

System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");
}

}


再编写一个日志的代理类,同样实现movable接口

/**
* 对日志的代理
* @author Administrator
*
*/
public class CarLogProxy implements Movable {

private Movable movable;

public CarLogProxy(Movable movable) {
super();
this.movable = movable;
}

@Override
public void move() {
System.out.println("日志开始..........");

movable.move();

System.out.println("日志结束");
}

}


在测试类中,我们实现先对日志记录,再对时间记录

public class Test2 {
public static void main(String[] args) {
Car car = new Car();
CarTimeProxy carTimeProxy = new CarTimeProxy(car);
CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy);
carLogProxy.move();

}
}


得到的结果如下

日志开始..........
汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:4毫秒
日志结束


如果想先记录汽车行驶的时间,就先创建CarLogProxy类,再创建CarTimeProxy类,并在CarTimeProxy类中创建CarLogProxy属性。

public class Test2 {
public static void main(String[] args) {
Car car = new Car();

CarLogProxy carLogProxy = new CarLogProxy(car);
CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy);
carTimeProxy.move();

}
}


此时控制台打印输出的结果为:

汽车开始行驶..........
日志开始..........
汽车行驶中、、、、、、、
日志结束
汽车停止行驶,行驶时间为:919毫秒


2.2 动态代理

此时实现的代理类只是对car的代理,如果再来一辆货车,还要实现TrainLogProxy 和TrainTimeProxy 两个类. 假如有100个类要做事件代理。就需要新建一百个代理类。

因此可以使用动态代理类,实现对不同类不同方法的代理。

动态代理的主要机制就是在类和代理类之间加入一个ProxyHandler(事务处理器)这样一个类

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

(1)Interface InvocationHandler:该接口仅定义了一个方法:

public Object invoke(Object obj,Method method, Object args[])

在实际使用中,第一个参数obj一般指代理类,method是指被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。

(2) Proxy:该类极为动态代理类

static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)

返回代理类的一个实例,返回后的代理类可以被当做代理类使用(可使用被代理类的在接口中声明过的方法)

我们可以通过一个实例来看一下

2.2.1 动态代理实例

首先新建一个TimeHandler(事件代理器)类,实现InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

//构造器
private Object target;

public TimeHandler(Object target) {
super();
this.target = target;
}

/**
* proxy:被代理的对象
* method:被代理对象的方法
* args 方法的参数
*
* 返回值
* Object方法的返回值
*/
@Override
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;//这里没有返回值
}

}


然后就可以在测试类里面测试

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new TimeHandler(car);
Class<?> cls = car.getClass();

/**
* loader 类加载器
* interfaces 实现接口
* h InvocationHandler
*/
Movable m = (Movable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
m.move();
}
}


这样就实现了对Car类的动态代理。

2.2.2 动态代理介绍

所谓Dynamic Proxy是这样一种class:

它是在运行时生成的class

该class需要实现一组interface

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

2.2.3 动态代理实现的步骤

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

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

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

Proxy.newProxyInstance(loader, interfaces, h)

4、调用代理类的方法。

2.2.4 CGLib动态代理

JDK动态代理:

1、只能代理实现了接口的类

2、没有实现接口的类不能实现JDK的动态代理

CGLIB动态代理:

1、针对类来实现代理的

2、对制定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

下面是CGLIB创建动态代理的一个实例:

首先在项目里面引入CGlib的jar包。jar包下载地:
http://download.csdn.net/download/beidiqiuren/9533454

我们先建立一个火车类,不实现任何接口,并添加一个move方法

public class Train {

public void move(){
System.out.println("火车正在行驶");
}
}


其次,添加一个动态代理类。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

private Enhancer enhancer = new Enhancer();

public Object getProxy(Class clazz){
//设置创建子类的类(被代理的类)
enhancer.setSuperclass(clazz);
//回调
enhancer.setCallback(this);

return enhancer.create();
}

/**
* 拦截所有目标类方法的调用
* obj 目标类的实例
* method 目标方法的反射对象
* args 方法的参数
* proxy 代理类的实例
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println("日志开始。。。。");

//代理类调用父类的方法
proxy.invokeSuper(obj, args);

System.out.println("日志结束。。。。");

return null;
}

}


这里之所以调用父类的代理方法是因为:intercept()方法拦截所有目标类方法的调用,如果调用proxy.invoke(obj, args);

会报java.lang.StackOverflowError异常,原因是将代理类作为目标类,这样会无限循环调用intercept方法,

导致无限死循环;二代理类的父类只有一个,调用父类的方法只会调用一次。

我们在测试类中测试一下

public class Test {
public static void main(String[] args) {

CglibProxy proxy = new CglibProxy();
Train t = (Train) proxy.getProxy(Train.class);

t.move();

}
}


测试结果为

日志开始。。。。
火车正在行驶
日志结束。。。。


可见代理已经成功。

3.模拟动态代理实现思路

动态代理实现思路

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

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

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

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

4、return 代理对象

下面我们来实际编写一下

3.1 声明一段源码

在此,我们使用一个外部的jar包简化上传下载的代码

jar包地址:http://download.csdn.net/download/beidiqiuren/9533455

package proxy;

import java.io.File;

import org.apache.commons.io.FileUtils;

public class Proxy {

public static Object newProxyInstance() throws Exception {

String rt = "\r\n";

String str = "package proxy;" + rt +

"public class CarTimeProxy implements Movable {" + rt +

"   private Movable movable;" + rt +

"public CarTimeProxy(Movable movable) {" + rt + "       super();" + rt + "      this.movable = movable;"
+ rt + "    }" + rt +

"   @Override" + rt + " public void move() {" + rt
+ "     long starttime = System.currentTimeMillis();" + rt
+ "     System.out.println(\"汽车开始行驶..........\");" + rt +

"       movable.move();" + rt +

"       long endtime = System.currentTimeMillis();" + rt +

"       System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" + rt + "   }" + rt +

"}";

// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy.java";
File file = new File(filename);

//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);

return null;
}

}


为了方便起见,代理类我们就使用之前编写好的CarTimeProxy

在测试类中运行

package proxy;

public class Test3 {
public static void main(String[] args) throws Exception {
Proxy.newProxyInstance();
}
}


运行之后,生成了一个新的java文件

接下来,我们进一步优化这个代理类

将代理类实现的接口实现动态调整,并通过动态获得应重写的方法。

3.2 优化一段源码

package proxy;

import java.io.File;
import java.lang.reflect.Method;

import org.apache.commons.io.FileUtils;

public class Proxy {

public static Object newProxyInstance(Class Infce) throws Exception {

String rt = "\r\n";

String methodStr = "";
for(Method m:Infce.getMethods()){
methodStr +="   @Override" + rt + " public void "+m.getName()+"() {" + rt
+ "     long starttime = System.currentTimeMillis();" + rt
+ "     System.out.println(\"汽车开始行驶..........\");" + rt +

"       movable."+m.getName()+"();" + rt +

"       long endtime = System.currentTimeMillis();" + rt +

"       System.out.println(\"汽车停止行驶,行驶时间为:\" + (endtime - starttime) + \"毫秒\");" +
rt + "  }" ;
}

String str = "package proxy;" + rt +

"public class $Proxy0 implements "+Infce.getName()+" {" + rt +

"   private "+Infce.getName()+"  movable;" + rt +

"public $Proxy0("+Infce.getName()+"  movable) {" + rt + "      super();" + rt + "      this.movable = movable;"
+ rt + "    }" + rt +

methodStr+rt +
"}";

// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
File file = new File(filename);

//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);

return null;
}

}


在测试类中运行,生成的$Proxy0.java文件并无不妥的地方,我们开始对这个文件进行编译。

3.3 编译源码

在Proxy类的newProxyInstance()中。我们对生成的文件进行编译

//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

//创建文件的管理者
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);

//根据文件名得到管理文件的数组
Iterable units = fileMgr.getJavaFileObjects(filename);

//编译任务
CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);

//进行编译
task.call();

fileMgr.close();


在测试类中运行

编译后,在bin目录下生成了一个$Proxy0.class文件。接下来就是把这个文件载入到内存当中。

3.4 将编译后的文件载入到内存当中

//load到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("proxy.$Proxy0");

//根据构造器初始化创建代理类
//构造器传递的参数就是接口的名字
Constructor ctr = c.getConstructor(Infce);

//根据构造器创建对象
return ctr.newInstance(new Car());


在测试类中测试

package proxy;

public class Test3 {
public static void main(String[] args) throws Exception {
Movable movable = (Movable) Proxy.newProxyInstance(Movable.class);
movable.move();
}
}


此时控制台打印输出的结果为

汽车开始行驶..........
汽车行驶中、、、、、、、
汽车停止行驶,行驶时间为:126毫秒


代理成功。

3.5 动态指定业务逻辑

在上面的案例中,业务逻辑需要代理的方法是写死在proxy类中的,而且需要传入Car这个类,如何动态的制定业务逻辑?

下面我们模拟一下真实的JDK Proxy

首先,新建一个InvocationHandler接口

package proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

public void invoke(Object o,Method m);

}


定义一个TimeHandler类实现InvocationHandler接口。

package proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

private Object target;

public TimeHandler(Object target) {
super();
this.target = target;

}

@Override
public void invoke(Object o, Method m) {
// TODO Auto-generated method stub
try {

long starttime = System.currentTimeMillis();
System.out.println("汽车开始行驶..........");

m.invoke(target);

long endtime = System.currentTimeMillis();
System.out.println("汽车停止行驶,行驶时间为:" + (endtime - starttime) + "毫秒");

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}


然后我们修改Proxy类,使其更加独立。

package proxy;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;

public class Proxy {

public static Object newProxyInstance(Class Infce,InvocationHandler h) throws Exception {

String rt = "\r\n";

String methodStr = "";
for(Method m:Infce.getMethods()){
methodStr +="   @Override" + rt +
"   public void "+m.getName()+"() {" + rt+
"try{"+
" Method md = "+Infce.getName()+".class.getMethod(\""+
m.getName()+"\");"+rt +
"h.invoke(this,md);"+rt +
"}catch(Exception e){e.printStackTrace();}"+

"   }" ;
}

String str = "package proxy;" + rt +"import proxy.InvocationHandler;"+rt+
"import java.lang.reflect.Method;"+rt+

"public class $Proxy0 implements "+Infce.getName()+" {" + rt +
"private InvocationHandler h;"+rt+
"public $Proxy0(InvocationHandler h) {" + rt
+ "     super();" + rt
+ "     this.h = h;"
+ rt + "    }" + rt +

methodStr+rt +
"}";

// 编译:先生成一个java文件,对这个文件进行编译
// System.getProperty("user.dir")当前路径
String filename = System.getProperty("user.dir") + "/bin/proxy/$Proxy0.java";
File file = new File(filename);

//FileUtils这个类可以快速的对文件进行删除和读写
FileUtils.writeStringToFile(file, str);

//得到当前系统的编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

//创建文件的管理者
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);

//根据文件名得到管理文件的数组
Iterable units = fileMgr.getJavaFileObjects(filename);

//编译任务
CompilationTask task = compiler.getTask(null, fileMgr, null, null, null, units);

//进行编译
task.call();

fileMgr.close();

//load到内存当中
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = cl.loadClass("proxy.$Proxy0");

//根据构造器初始化创建代理类
//构造器传递的参数就是接口的名字
Constructor ctr = c.getConstructor(InvocationHandler.class);

//根据构造器创建对象
return ctr.newInstance(h);

}

}


最后在测试类中测试:

package proxy;

public class Test3 {
public static void main(String[] args) throws Exception {
Car car = new Car();

InvocationHandler h = new TimeHandler(car);

Movable movable = (Movable) Proxy.newProxyInstance(Movable.class,h);
movable.move();
}
}


成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息