【设计模式 - 12】之代理模式(Proxy)
1、模式简介
代理模式的简介:
为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可以去掉功能服务或增加额外服务。
常见的代理模式:
- 远程代理:可以隐藏一个对象存在于不同控件的事实,也使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求;
- 虚拟代理:根据需要将资源消耗很大或比较复杂的对象进行延迟,真正需要的时候才进行创建(如:加载图片时显示的图片占位符);
- 保护代理:为不同用户提供不同级别的目标对象访问权限;
- 智能引用代理:当一个对象被引用时,提供一些额外的操作;
- 缓存代理:为开销大的运算结果提供暂时存储,允许多个用户共享结果,以减少计算或网络延迟;
- 同步代理:在多线程的情况下为主题提供安全的访问;
- 外观代理:用来隐藏一个类的复杂集合的复杂度,并进行访问控制。
2、案例
本文中使用上面说的智能引用代理为例简单介绍代理模式。模拟汽车行驶的功能,在汽车行驶前后进行计时,最终得到汽车行驶时间。
2.1、静态代理方式
下面的示例代码中,CarProxy1类使用了继承方式进行代理,CarProxy2类使用了聚合方式进行代理。
继承方式和聚合方式的比较:
在上面的例子中,我们只对时间进行了代理,如果我们在实际开发中不仅要对时间进行代理,还需要对日志、权限等进行代理,那么如果使用继承方式代理,则我们可能会创建很多类,有日志à时间à权限顺序的,有权限à时间à日志顺序的,等等;而如果我们使用聚合方式进行代理,则我们只需要创建时间代理类、日志代理类和权限代理类这三个代理类,然后根据需求调整其调用顺序即可。由此可见,聚合方式比继承方式要好用。
静态代理方式的代码如下:
交通工具的(行驶)接口代码:
public interface Movable { // 汽车行驶 void move(); }
汽车类代码:
public class Car implements Movable { @Override public void move() { try { System.out.println("汽车正在行驶......"); Thread.sleep((long) (Math.random() * 1000 + 1)); } catch (InterruptedException e) { e.printStackTrace(); } } }
继承方式的静态代理类CarProxy1中的代码:
public class CarProxy1 extends Car { @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶......"); super.move(); long endTime = System.currentTimeMillis(); System.out.println("汽车停止行驶,共行驶了" + (endTime - startTime) + "毫秒"); } }
聚合方式的静态代理类CarProxy2中的代码:
public class CarProxy2 implements Movable { private Car car; public CarProxy2(Car car) { 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) + "毫秒"); } }
测试类Test中的代码:
public class Test { public static void main(String[] args) { // 使用继承方式的静态代理 // Movable movable = new CarProxy1(); // movable.move(); // 使用聚合方式的静态代理(通过分析,这种方法更适合代理模式) Movable movable = new CarProxy2(new Car()); movable.move(); } }
运行结果如下图所示:
2.2、动态代理方式
这里我们使用JDK提供的动态代理模式编写代码。JDK中的动态代理的原理是利用反射机制在运行的时候动态的产生目标类的.class字节码文件,然后使用JDK提供的InvocationHandler接口中的invoke()方法调用目标类中的方法或在此基础上添加更多的业务逻辑,最后调用Proxy.newProxyInstance()方法创建代理类并调用方法。
动态代理步骤:
- 创建一个实现了InvocationHandler接口的类,实现invoke()方法;
- 创建被代理的类及接口;
- 调用Proxy的静态方法newProxyInstance()创建一个代理类;
- 通过代理调用方法。
JDK自从1.3版本开始就引入了动态代理,JDK的动态代理用起来非常简单,但是它有一个显示,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,可以使用cglib包中提供的API。动态代理方式的代码如下:
交通工具接口Vehicle的代码如下:
public interface Vehicle { // 交通工具行驶方法 void move(); }
汽车类Car的代码如下:
public class Car implements Vehicle { @Override public void move() { try { System.out.println("汽车行驶中......"); Thread.sleep((long) (Math.random() * 1000 + 1)); } catch (InterruptedException e) { e.printStackTrace(); } } }
时间业务处理类TimeInvocationHandler中的代码如下:
public class TimeInvocationHandler implements InvocationHandler { private Object target; public TimeInvocationHandler(Object target) { this.target = target; } /** * 参数:<br/> * proxy:被代理的对象<br/> * method:被代理对象的方法<br/> * args:方法中的参数<br/> * </p> * 返回值:<br/> * Object:被代理对象的方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 业务逻辑 long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶......"); // 调用被代理对象中的方法 method.invoke(target, args); // 业务逻辑 long endTime = System.currentTimeMillis(); System.out.println("汽车停止行驶,共行驶了" + (endTime - startTime) + "毫秒"); return null; } }
测试类Test中的代码如下:
public class Test { public static void main(String[] args) { Car car = new Car(); Class<?> carClass = car.getClass(); TimeInvocationHandler timeHandler = new TimeInvocationHandler(car); /** * loader:被代理类的类加载器<br/> * interfaces:被代理类实现的接口<br/> * h:InvocationHandler */ Vehicle v = (Vehicle) Proxy.newProxyInstance(carClass.getClassLoader(), carClass.getInterfaces(), timeHandler); v.move(); } }
运行结果如下图所示:
最后贴出代理模式的GitHub代码的地址:【GitHub - Proxy】。