国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

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

2019-11-26 14:18:54
字體:
來源:轉載
供稿:網友

一、代理模式定義

給某個對象提供一個代理對象,并由代理對象控制對于原對象的訪問,即客戶不直接操控原對象,而是通過代理對象間接地操控原對象。
著名的代理模式的例子就是引用計數(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、代理模式的參與者

代理模式的角色分四種:

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

201652095314645.jpg (405×252)

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的結構圖。

201652095547707.jpg (684×818)

可以看出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'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方法。
就此代理模式分析到此結束。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 无极县| 张北县| 石台县| 博乐市| 都安| 茶陵县| 翁源县| 旬邑县| 滁州市| 历史| 津市市| 屏东市| 武宁县| 美姑县| 志丹县| 柏乡县| 双辽市| 沛县| 盐津县| 屏山县| 临朐县| 新昌县| 九江县| 铜川市| 本溪市| 泽库县| 双鸭山市| 漾濞| 昔阳县| 九台市| 胶南市| 东明县| 通城县| 鹤峰县| 长丰县| 高碑店市| 安西县| 屏山县| 蒲江县| 宁晋县| 广汉市|