高级程序员必须掌握的编程语言 (高级程序员实用技能)

在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式

代理模式和经典模式,代理模式的设计思想

角色说明:

  • Subject(抽象主题类):接口或者抽象类,声明真实主题与代理的共同接口方法。
  • RealSubject(真实主题类):也叫做被代理类或被委托类,定义了代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接的调用真实主题类的方法。
  • Proxy(代理类):也叫委托类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
  • Client(客户端类):使用代理模式的地方。

以海外代购为例,在国内的人想买国外的东西只能去找国外的人去进行代购

 public interface People {
 void buy();//购买
 }
 
 // 实际类
 public class Domestic implements People {
 @Override
 public void buy() {//具体实现
 System.out.println("国内要买一个包");
 }
 }
 

 // 代理类
 public class Oversea implements People {
 People mPeople;//持有People类的引用
 public Oversea(People people) {
 mPeople = people;
 }
 @Override
 public void buy() {
 System.out.println("我是海外代购:");
 mPeople.buy();//调用了被代理者的buy()方法,
 }
 }
 public void test() {
 People domestic = new Domestic(); //创建国内购买人
 People oversea = new Oversea(domestic); //创建海外代购类并将domestic作为构造函数传递
 oversea.buy(); //调用海外代购的buy()
 }
 
 // 输出结果:
 // 我是海外代购:
 // 国内要买一个包

上面讲的是静态代理, 静态代理的局限在于运行前必须编写好代理类,下面我们来介绍下运行时生成代理类的动态代理方式

动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定

Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法

public class DynamicProxy implements InvocationHandler {//实现InvocationHandler接口
 private Object obj;//被代理的对象
 public DynamicProxy(Object obj) {
 this.obj = obj;
 }
 //重写invoke()方法
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 System.out.println("海外动态代理调用方法:"+method.getName());
 Object result = method.invoke(obj, args);//调用被代理的对象的方法
 return result;
 }
 }
 public void test() {
 People domestic = new Domestic(); //创建国内购买人
 DynamicProxy proxy = new DynamicProxy(domestic); //创建动态代理
 ClassLoader classLoader = domestic.getClass().getClassLoader(); //获取ClassLoader
 People oversea = (People) Proxy.newProxyInstance(classLoader, new Class[]{People.class}, proxy); //通过 Proxy 创建海外代购实例 ,实际上通过反射来实现的。
 oversea.buy();//调用海外代购的buy()
 }

在以上代码中,我们调用Proxy类的newProxyInstance方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。这个方法的声明如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

方法的三个参数含义分别如下:

loader:定义了代理类的ClassLoder; interfaces:代理类实现的接口列表 h:调用处理器,也就是我们上面定义的实现了InvocationHandler接口的类实例

// 运行结果
海外动态代理调用方法:buy
国内要买一个包

代理模式的优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

代理模式的缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。

应用场景

  • 当一个对象不能或者不想直接访问另一个对象时,可以通过一个代理对象来间接访问。为保证客户端使用的透明性,委托对象和代理对象要实现同样的接口。
  • 被访问的对象不想暴露全部内容时,可以通过代理去掉不想被访问的内容。
  • 根据适用范围,代理模式可以分为以下几种:
  • 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。
  • 虚拟代理:如果要创建一个资源消耗较大的对象,可以先用一个代理对象表示,在真正需要的时候才真正创建。
  • 保护代理:用代理对象控制对一个对象的访问,给不同的用户提供不同的访问权限。
  • 智能引用:在引用原始对象的时候附加额外操作,并对指向原始对象的引用增加引用计数。

总结

在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。

代理模式包含三个角色:抽象主题角色声明了真实主题和代理主题的共同接口;代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。

代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。