程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 詳解設計形式中的proxy署理形式及在Java法式中的完成

詳解設計形式中的proxy署理形式及在Java法式中的完成

編輯:關於JAVA

詳解設計形式中的proxy署理形式及在Java法式中的完成。本站提示廣大學習愛好者:(詳解設計形式中的proxy署理形式及在Java法式中的完成)文章只能為提供參考,不一定能成為您想要的結果。以下是詳解設計形式中的proxy署理形式及在Java法式中的完成正文


1、署理形式界說

給某個對象供給一個署理對象,並由署理對象掌握關於原對象的拜訪,即客戶不直接操控原對象,而是經由過程署理對象直接地操控原對象。
有名的署理形式的例子就是援用計數(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呼應發還給用戶


2、靜態署理

所謂靜態署理, 就是在編譯階段就生成署理類來完成對署理對象的一系列操作。上面是署理形式的構造類圖:

1、署理形式的介入者

署理形式的腳色分四種:

主題接口: 即署理類的所完成的行動接口。
目的對象: 也就是被署理的對象。
署理對象: 用來封裝真是主題類的署理類
客戶端
上面是署理形式的類圖構造:

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辦法。其他的例子還有許多, 詳細須要本身領會。

3、靜態署理

靜態署理是指在運轉時靜態生成署理類。即,署理類的字節碼將在運轉時生成並載入以後署理的 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滿是靜態辦法, 是以假如署理類沒有完成任何接口, 那末他就是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);
  }
}

4、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't successful in installing the CacheValue)
    //上面這個進程就是填充supplier的進程
    if(factory == null) {
      //創立一個factory
    }
    if(supplier == null) {
      //填充supplier
    }else {
      //填充supplier
    }
  }

while輪回的感化就是一直的獲得完成InvokeHandler的類, 這個類可所以從緩存中拿到,也可是是從proxyFactoryClass生成的。
Factory是一個完成了Supplier<V>接口的外部類。這個類籠罩了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('.');
      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' 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辦法。
就此署理形式剖析到此停止。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved