首頁 >Java >java教程 >詳解設計模式中的proxy代理模式及在Java程式中的實現

詳解設計模式中的proxy代理模式及在Java程式中的實現

高洛峰
高洛峰原創
2017-02-07 13:20:321626瀏覽

一、代理模式定義

給某個對象提供一個代理對象,並由代理對象控制對於原對象的訪問,即客戶不直接操控原對象,而是透過代理對象間接地操控原對象。
著名的代理模式的例子就是引用計數(reference counting): 當需要一個複雜物件的多份副本時, 代理模式可以結合享元模式以減少記憶體的用量。典型做法是建立一個複雜物件以及多個代理者, 每個代理者會引用到原本的物件。而作用在代理者的運算會轉送到原本物件。一旦所有的代理者都不存在時, 複雜物件會被移除。

要理解代理模式很簡單,其實生活當中就存在代理模式:
我們購買火車票可以去火車站買,但是也可以去火車票代售處買,此處的火車票代售處就是火車站購票的代理,即我們在代售點發出買票請求,代售點會把請求發給火車站,火車站把購買成功響應發給代售點,代售點再告訴你。
但是代售點只能買票,不能退票,而火車站能買票也能退票,因此代理對象支援的操作可能和委託對象的操作有所不同。

再舉一個寫程式會碰到的一個例子:
如果現在有一個已有項目(你沒有原始碼,只能呼叫它)能夠呼叫int compute(String exp1) 實作對於後綴表達式的計算,你想使用這個項目實作對於中綴表達式的計算,那麼你可以寫一個代理類,並且其中也定義一個compute(String exp2),這個exp2參數是中綴表達式,因此你需要在呼叫已有項目的compute() 之前將中綴表達式轉換成後綴表達式(Preprocess),再調用已有項目的compute(),當然你還可以接收到返回值之後再做些其他操作比如存入文件(Postprocess),這個過程就是使用了代理模式。

在平常用電腦也會碰到代理模式的應用:
遠端代理:我們在國內因為GFW,所以不能訪問 facebook,我們可以用翻牆(設定代理)的方法存取。訪問過程是:
(1)用戶把HTTP請求發給代理
(2)代理把HTTP請求發給web伺服器
(3)web伺服器把HTTP回應發給代理
(4)代理把HTTP回應傳回給使用者


二、靜態代理

所謂靜態代理, 就是在編譯階段就產生代理類別來完成對代理物件的一系列操作。以下是代理模式的結構類別圖:

1、代理模式的參與者

代理模式的角色分為四種:

主題介面: 即代理類別的所實現的行為介面。
目標物件: 也就是被代理的物件。
代理物件: 用來封裝真是主題類別的代理類別
客戶端 
下面是代理模式的類別圖結構: 

詳解設計模式中的proxy代理模式及在Java程式中的實現

2、代理模式的實作思維

代理物件和目標物件都實現一個行為物件接口。
代理類別和目標類別分別具體實作介面邏輯。
在代理類別的建構子中實例化一個目標物件。
在代理類別中呼叫目標物件的行為介面。
客戶端想要呼叫目標物件的行為接口,只能透過代理類別來操作。
3、靜態代理的實例

下面以一個延遲載入的例子來說明靜態代理。我們在啟動某個服務系統時, 載入某一個類別時可能會耗費很久。為了獲得更好的效能, 在啟動系統的時候, 我們往往不去初始化這個複雜的類, 取而代之的是去初始化其代理類別。這樣將耗費資源多的方法使用代理進行分離, 可以加快系統的啟動速度, 減少使用者等待的時間。

定義一個主題介面

public interface Subject {
  public void sayHello();
  public void sayGoodBye();
}

定義一個目標類, 並實現主題介面

public class RealSubject implements Subject {
  public void sayHello() {
    System.out.println("Hello World");
  }
  public void sayGoodBye() {
    System.out.println("GoodBye World");
  }
}

定義一個代理類, 來代理目標物件。

public class StaticProxy implements Subject {
  Private RealSubject realSubject = null;
  public StaticProxy() {}
  public void sayHello() {
    //用到时候才加载, 懒加载
    if(realSubject == null) {
      realSubject = new RealSubject();
    }
    realSubject.sayHello();
  }
  //sayGoodbye方法同理
  ...
}

定義一個客戶端

public class Client {
  public static void main(String [] args) {
    StaticProxy sp = new StaticProxy();
    sp.sayHello();
    sp.sayGoodBye();
  }
}

以上就是靜態代理的一個簡單測試範例。感覺可能沒有實際用途。然而並非如此。使用代理我們也可以將目標物件的方法進行改造, 例如在資料庫連接池中創建了一系列連接, 為了確保不頻繁的開啟連接,這些連接是幾乎不會關閉的。然而我們程式總有習慣去將打開的Connection去close。 這樣我們就可以利用代理模式來重新代理Connection介面中的close方法, 改變為回收到資料庫連線池而不是真正的執行Connection#close方法。其他的例子還有很多, 具體需要自己體會。

三、動態代理

動態代理是指在運行時動態產生代理類別。即,代理類別的字節碼將在運行時產生並載入目前代理的 ClassLoader。與靜態處理類別相比,動態類別有許多好處。

不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;
使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
生成动态代理的方法有很多: JDK中自带动态代理, CGlib, javassist等。这些方法各有优缺点。本文主要探究JDK中的动态代理的使用和源码分析。

下面用一个实例讲解一下JDK中动态代理的用法:

public class dynamicProxy implements InvocationHandler {
  private RealSubject = null;
  public Object invoke(Object proxy, Method method, Object[] args){
    if(RealSubject == null) {
      RealSubject = new RealSubject();
    }
    method.invoke(RealSubject, args);
    return RealSubject;
  }
}

客户端代码实例

public class Client {
  public static void main(Strings[] args) {
    Subject subject = (Subject)Proxy.newInstance(ClassLoader.getSystemLoader(), RealSubject.class.getInterfaces(), new DynamicProxy());
    Subject.sayHello();
    Subject.sayGoodBye();
  }
}

从上面的代码可以看出, 要利用JDK中的动态代理。利用静态方法Proxy.newInstance(ClassLoader, Interfaces[], InvokeHandler)可以创建一个动态代理类。 newInstance方法有三个参数, 分别表示类加载器, 一个希望该代理类实现的接口列表, 以及实现InvokeHandler接口的实例。 动态代理将每个方法的执行过程则交给了Invoke方法处理。

JDK动态代理要求, 被代理的必须是个接口, 单纯的类则不行。JDK动态代理所生成的代理类都会继承Proxy类,同时代理类会实现所有你传入的接口列表。因此可以强制类型转换成接口类型。 下面是Proxy的结构图。 

詳解設計模式中的proxy代理模式及在Java程式中的實現

可以看出Proxy全是静态方法, 因此如果代理类没有实现任何接口, 那么他就是Proxy类型, 没有实例方法。

当然加入你要是非要代理一个没有实现某个接口的类, 同时该类的方法与其他接口定义的方法相同, 利用反射也是可以轻松实现的。

public class DynamicProxy implements InvokeHandler {
  //你想代理的类
  private TargetClass targetClass = null;
  //初始化该类
  public DynamicProxy(TargetClass targetClass) {
    this.targetClass = targetClass;
  }
  public Object invoke(Object proxy, Method method, Object[] args) {
    //利用反射获取你想代理的类的方法
    Method myMethod = targetClass.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
    myMethod.setAccessible(true);
    return myMethod.invoke(targetClass, args);
  }
}

四、JDK动态代理源码分析(JDK7)

看了上面的例子, 我们只是简单会用动态代理。但是对于代理类是如何创建出来的, 是谁调用Invoke方法等还云里雾里。下面通过分析

1、代理对象是如何创建出来的?

首先看Proxy.newInstance方法的源码:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
  }
  //获取接口信息
  final Class<?>[] intfs = interfaces.clone();
  final SecurityManager sm = System.getSecurityManager();
  if (sm != null) {
    checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  }
  //生成代理类
  Class<?> cl = getProxyClass0(loader, intfs);
  // ...OK我们先看前半截
  }

从源码看出代理类的生成是依靠getProxyClass0这个方法, 接下来看getProxyClass0源码:

private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
  //接口列表数目不能超过0xFFFF
  if (interfaces.length > 65535) {
    throw new IllegalArgumentException("interface limit exceeded");
  }
  //注意这里, 下面详细解释
    return proxyClassCache.get(loader, interfaces);
  }

对proxyClassCache.get的解释是: 如果实现接口列表的代理类已经存在,那么直接从cache中拿。如果不存在, 则通过ProxyClassFactory生成一个。 
在看proxyClassCache.get源码之前,先简单了解一下proxyClassCache:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
   proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

proxyClassCache是一个WeakCache类型的缓存, 它的构造函数有两个参数, 其中一个就是用于生成代理类的ProxyClassFactory, 下面是proxyClassCache.get的源码:

final class WeakCache<K, P, V> {
  ...
  public V get(K key, P parameter) {}
}

这里K表示key, P表示parameters, V表示value

public V get(K key, P parameter) {
  //java7 NullObject判断方法, 如果parameter为空则抛出带有指定消息的异常。 如果不为空则返回。
  Objects.requireNonNull(parameter);
  //清理持有弱引用的WeakHashMap这种数据结构,一般用于缓存
  expungeStaleEntries();
  //从队列中获取cacheKey
  Object cacheKey = CacheKey.valueOf(key, refQueue);
  //利用懒加载的方式填充Supplier, Concurrent是一种线程安全的map
  ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
  if (valuesMap == null) {
    ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
      if (oldValuesMap != null) {
        valuesMap = oldValuesMap;
      }
    }
    // create subKey and retrieve the possible Supplier<V> stored by that
    // subKey from valuesMap
  Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
  Supplier<V> supplier = valuesMap.get(subKey);
  Factory factory = null;
  while (true) {
    if (supplier != null) {
    // 从supplier中获取Value,这个Value可能是一个工厂或者Cache的实
    //下面这三句代码是核心代码, 返回实现InvokeHandler的类并包含了所需要的信息。
    V value = supplier.get();
      if (value != null) {
        return value;
      }
    }
    // else no supplier in cache
    // or a supplier that returned null (could be a cleared CacheValue
    // or a Factory that wasn&#39;t successful in installing the CacheValue)
    //下面这个过程就是填充supplier的过程
    if(factory == null) {
      //创建一个factory
    }
    if(supplier == null) {
      //填充supplier
    }else {
      //填充supplier
    }
  }

while循环的作用就是不停的获取实现InvokeHandler的类, 这个类可以是从缓存中拿到,也可是是从proxyFactoryClass生成的。 
Factory是一个实现了Supplier接口的内部类。这个类覆盖了get方法, 在get方法中调用了类型为proxyFactoryClass的实例方法apply。这个方法才是真正创建代理类的方法。下面看ProxyFactoryClass#apply方法的源码:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
  for (Class<?> intf : interfaces) {
    /* Verify that the class loader resolves the name of this interface to the same Class object.*/
  Class<?> interfaceClass = null;
    try {
      //加载每一个接口运行时的信息
      interfaceClass = Class.forName(intf.getName(), false, loader);
    } catch (ClassNotFoundException e) {
    }
  //如果使用你自己的classload加载的class与你传入的class不相等,抛出异常
  if (interfaceClass != intf) {
    throw new IllegalArgumentException(
    intf + " is not visible from class loader");
  }
  //如果传入不是一个接口类型
    if (!interfaceClass.isInterface()) {
      throw new IllegalArgumentException(
        interfaceClass.getName() + " is not an interface");
    }
   //验证接口是否重复
    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
      throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
    }
  }
  String proxyPkg = null;   // package to define proxy class in
  /* Record the package of a non-public proxy interface so that the proxy class will be defined in the same package.
  * Verify that all non-public proxy interfaces are in the same package.
  */
  //这一段是看你传入的接口中有没有不是public的接口,如果有,这些接口必须全部在一个包里定义的,否则抛异常
  for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    if (!Modifier.isPublic(flags)) {
      String name = intf.getName();
      int n = name.lastIndexOf(&#39;.&#39;);
      String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
      if (proxyPkg == null) {
        proxyPkg = pkg;
      } else if (!pkg.equals(proxyPkg)) {
        throw new IllegalArgumentException(
          "non-public interfaces from different packages");
      }
    }
  }
  if (proxyPkg == null) {
    // if no non-public proxy interfaces, use com.sun.proxy package
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  }
  /*
  * Choose a name for the proxy class to generate.
  */
  long num = nextUniqueNumber.getAndIncrement();
  //生成随机代理类的类名, $Proxy + num
  String proxyName = proxyPkg + proxyClassNamePrefix + num;
  /*
  * 生成代理类的class文件, 返回字节流
  */
  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
  try {
    return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
  } catch (ClassFormatError e) {
        //结束
        throw new IllegalArgumentException(e.toString());
      }
    }
  }

前文提到ProxyFactoryClass#apply是真正生成代理类的方法, 这其实是不准确的。源代码读到这里,我们会发现ProxyGenerator#generateProxyClass才是真正生成代理类的方法。根据Java class字节码组成(可以参见我的另一篇文章Java字节码学习笔记)来生成相应的Clss文件。具体ProxyGenerator#generateProxyClass源码如下:

private byte[] generateClassFile() {
   /*
    * Step 1: Assemble ProxyMethod objects for all methods to
    * generate proxy dispatching code for.
    */
    //addProxyMethod方法,就是将方法都加入到一个列表中,并与对应的class对应起来
   //这里给Object对应了三个方法hashCode,toString和equals
   addProxyMethod(hashCodeMethod, Object.class);
   addProxyMethod(equalsMethod, Object.class);
   addProxyMethod(toStringMethod, Object.class);
   //将接口列表中的接口与接口下的方法对应起来
   for (int i = 0; i < interfaces.length; i++) {
     Method[] methods = interfaces[i].getMethods();
     for (int j = 0; j < methods.length; j++) {
       addProxyMethod(methods[j], interfaces[i]);
     }
   }
   /*
    * For each set of proxy methods with the same signature,
    * verify that the methods&#39; return types are compatible.
    */
   for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
     checkReturnTypes(sigmethods);
   }
   /*
    * Step 2: Assemble FieldInfo and MethodInfo structs for all of
    * fields and methods in the class we are generating.
    */
    //方法中加入构造方法,这个构造方法只有一个,就是一个带有InvocationHandler接口的构造方法
    //这个才是真正给class文件,也就是代理类加入方法了,不过还没真正处理,只是先加进来等待循环,构造方法在class文件中的名称描述是<init>
 try {
   methods.add(generateConstructor());
   for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
     for (ProxyMethod pm : sigmethods) {
//给每一个代理方法加一个Method类型的属性,数字10是class文件的标识符,代表这些属性都是private static的
       fields.add(new FieldInfo(pm.methodFieldName,
         "Ljava/lang/reflect/Method;",
          ACC_PRIVATE | ACC_STATIC));
       //将每一个代理方法都加到代理类的方法中
       methods.add(pm.generateMethod());
     }
   }
 //加入一个静态初始化块,将每一个属性都初始化,这里静态代码块也叫类构造方法,其实就是名称为<clinit>的方法,所以加到方法列表
     methods.add(generateStaticInitializer());
   } catch (IOException e) {
     throw new InternalError("unexpected I/O Exception");
   }
 //方法和属性个数都不能超过65535,包括之前的接口个数也是这样,
 //这是因为在class文件中,这些个数都是用4位16进制表示的,所以最大值是2的16次方-1
   if (methods.size() > 65535) {
     throw new IllegalArgumentException("method limit exceeded");
   }
   if (fields.size() > 65535) {
     throw new IllegalArgumentException("field limit exceeded");
   }
 //接下来就是写class文件的过程, 包括魔数, 类名,常量池等一系列字节码的组成,就不一一细说了。需要的可以参考JVM虚拟机字节码的相关知识。
   cp.getClass(dotToSlash(className));
   cp.getClass(superclassName);
   for (int i = 0; i < interfaces.length; i++) {
     cp.getClass(dotToSlash(interfaces[i].getName()));
   }
   cp.setReadOnly();
   ByteArrayOutputStream bout = new ByteArrayOutputStream();
   DataOutputStream dout = new DataOutputStream(bout);
   try {
                   // u4 magic;
     dout.writeInt(0xCAFEBABE);
                   // u2 minor_version;
     dout.writeShort(CLASSFILE_MINOR_VERSION);
                   // u2 major_version;
     dout.writeShort(CLASSFILE_MAJOR_VERSION);
     cp.write(dout);       // (write constant pool)
                   // u2 access_flags;
     dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
                   // u2 this_class;
     dout.writeShort(cp.getClass(dotToSlash(className)));
                   // u2 super_class;
     dout.writeShort(cp.getClass(superclassName));
                   // u2 interfaces_count;
     dout.writeShort(interfaces.length);
                   // u2 interfaces[interfaces_count];
     for (int i = 0; i < interfaces.length; i++) {
       dout.writeShort(cp.getClass(
         dotToSlash(interfaces[i].getName())));
     }
                   // u2 fields_count;
     dout.writeShort(fields.size());
                   // field_info fields[fields_count];
     for (FieldInfo f : fields) {
       f.write(dout);
     }
                   // u2 methods_count;
     dout.writeShort(methods.size());
                   // method_info methods[methods_count];
     for (MethodInfo m : methods) {
       m.write(dout);
     }
                    // u2 attributes_count;
     dout.writeShort(0); // (no ClassFile attributes for proxy classes)
   } catch (IOException e) {
     throw new InternalError("unexpected I/O Exception");
   }
   return bout.toByteArray();
 }

经过层层调用, 一个代理类终于生成了。

2、是谁调用了Invoke?

我们模拟JDK自己生成一个代理类, 类名为TestProxyGen:

public class TestGeneratorProxy {
  public static void main(String[] args) throws IOException {
    byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", Subject.class.getInterfaces());
    File file = new File("/Users/yadoao/Desktop/TestProxyGen.class");
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(classFile);
    fos.flush();
    fos.close();
  }
}

用JD-GUI反编译该class文件, 结果如下:

import com.su.dynamicProxy.ISubject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class TestProxyGen extends Proxy
 implements ISubject
{
 private static Method m3;
 private static Method m1;
 private static Method m0;
 private static Method m4;
 private static Method m2;
 public TestProxyGen(InvocationHandler paramInvocationHandler)
  throws
 {
  super(paramInvocationHandler);
 }
 public final void sayHello()
  throws
 {
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }
 public final boolean equals(Object paramObject)
  throws
 {
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }
 public final int hashCode()
  throws
 {
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }
 public final void sayGoodBye()
  throws
 {
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }
 public final String toString()
  throws
 {
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (Error|RuntimeException localError)
  {
   throw localError;
  }
  catch (Throwable localThrowable)
  {
   throw new UndeclaredThrowableException(localThrowable);
  }
 }
 static
 {
  try
  {
   m3 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayHello", new Class[0]);
   m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
   m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
   m4 = Class.forName("com.su.dynamicProxy.ISubject").getMethod("sayGoodBye", new Class[0]);
   m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
   return;
  }
  catch (NoSuchMethodException localNoSuchMethodException)
  {
   throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
  }
  catch (ClassNotFoundException localClassNotFoundException)
  {
   throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
 }
}

首先注意到生成代理类的构造函数, 它传入一个实现InvokeHandler接口的类作为参数, 并调用父类Proxy的构造器, 即将Proxy中的成员变量protected InvokeHander h进行了初始化。
再次注意到几个静态的初始化块, 这里的静态初始化块就是对代理的接口列表以及hashcode,toString, equals方法进行初始化。
最后就是这几个方法的调用过程, 全都是回调Invoke方法。
就此代理模式分析到此结束。

更多詳解設計模式中的proxy代理模式及在Java程式中的實現相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn