JAVA中的動態代理

1. 相關概念

1.1 代理

在某些情況下,我們不希望或是不能直接訪問對象 A,而是通過訪問一箇中介對象 B,由 B 去訪問 A 達成目的,這種方式我們就稱爲代理。
這裏對象 A 所屬類我們稱爲委託類,也稱爲被代理類,對象 B 所屬類稱爲代理類。
代理優點有:

  • 隱藏委託類的實現
  • 解耦,不改變委託類代碼情況下做一些額外處理,比如添加初始判斷及其他公共操作

根據程序運行前代理類是否已經存在,可以將代理分爲靜態代理和動態代理。

1.2 靜態代理

代理類在程序運行前已經存在的代理方式稱爲靜態代理。
通過上面解釋可以知道,由開發人員編寫或是編譯器生成代理類的方式都屬於靜態代理,如下是簡單的靜態代理實例:

class ClassA {
    public void operateMethod1() {};

    public void operateMethod2() {};

    public void operateMethod3() {};
}

public class ClassB {
    private ClassA a;

    public ClassB(ClassA a) {
        this.a = a;
    }

    public void operateMethod1() {
        a.operateMethod1();
    };

    public void operateMethod2() {
        a.operateMethod2();
    };

    // not export operateMethod3()
}

上面ClassA是委託類,ClassB是代理類,ClassB中的函數都是直接調用ClassA相應函數,並且隱藏了ClassoperateMethod3()函數。

靜態代理中代理類和委託類也常常繼承同一父類或實現同一接口。

1.3 動態代理

代理類在程序運行前不存在、運行時由程序動態生成的代理方式稱爲動態代理。

Java 提供了動態代理的實現方式,可以在運行時刻動態生成代理類。這種代理方式的一大好處是可以方便對代理類的函數做統一或特殊處理,如記錄所有函數執行時間、所有函數執行前添加驗證判斷、對某個特殊函數進行特殊操作,而不用像靜態代理方式那樣需要修改每個函數。

靜態代理比較簡單,本文上面已簡單介紹,下面重點介紹動態代理

2. 動態代理實例

實現動態代理包括三步:

(1). 新建委託類;
(2). 實現InvocationHandler接口,這是負責連接代理類和委託類的中間類必須實現的接口;
(3). 通過Proxy類新建代理類對象。

下面通過實例具體介紹,假如現在我們想統計某個類所有函數的執行時間,傳統的方式是在類的每個函數前打點統計,動態代理方式如下:

2.1 新建委託類

public interface Operate {

    public void operateMethod1();

    public void operateMethod2();

    public void operateMethod3();
}

public class OperateImpl implements Operate {

    @Override
    public void operateMethod1() {
        System.out.println("Invoke operateMethod1");
        sleep(110);
    }

    @Override
    public void operateMethod2() {
        System.out.println("Invoke operateMethod2");
        sleep(120);
    }

    @Override
    public void operateMethod3() {
        System.out.println("Invoke operateMethod3");
        sleep(130);
    }

    private static void sleep(long millSeconds) {
        try {
            Thread.sleep(millSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Operate是一個接口,定了了一些函數,我們要統計這些函數的執行時間。
OperateImpl是委託類,實現Operate接口。每個函數簡單輸出字符串,並等待一段時間。
動態代理要求委託類必須實現了某個接口,比如這裏委託類OperateImpl實現了Operate,原因會後續在微博公佈。

2.2. 實現 InvocationHandler 接口

public class TimingInvocationHandler implements InvocationHandler {

    private Object target;

    public TimingInvocationHandler() {}

    public TimingInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object obj = method.invoke(target, args);
        System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
        return obj;
    }
}

target屬性表示委託類對象。

InvocationHandler是負責連接代理類和委託類的中間類必須實現的接口。其中只有一個

public Object invoke(Object proxy, Method method, Object[] args)

函數需要去實現,參數:
proxy表示下面2.3 通過 Proxy.newProxyInstance() 生成的代理類對象
method表示代理對象被調用的函數。
args表示代理對象被調用的函數的參數。

調用代理對象的每個函數實際最終都是調用了InvocationHandlerinvoke函數。這裏我們在invoke實現中添加了開始結束計時,其中還調用了委託類對象target的相應函數,這樣便完成了統計執行時間的需求。
invoke函數中我們也可以通過對method做一些判斷,從而對某些函數特殊處理。

2.3. 通過 Proxy 類靜態函數生成代理對象

public class Main {
    public static void main(String[] args) {
        // create proxy instance
        TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
        Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
                timingInvocationHandler));

        // call method of proxy instance
        operate.operateMethod1();
        System.out.println();
        operate.operateMethod2();
        System.out.println();
        operate.operateMethod3();
    }
}

這裏我們先將委託類對象new OperateImpl()作爲TimingInvocationHandler構造函數入參創建timingInvocationHandler對象;
然後通過Proxy.newProxyInstance(…)函數新建了一個代理對象,實際代理類就是在這時候動態生成的。我們調用該代理對象的函數就會調用到timingInvocationHandlerinvoke函數(是不是有點類似靜態代理),而invoke函數實現中調用委託類對象new OperateImpl()相應的 method(是不是有點類似靜態代理)。

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

loader表示類加載器
interfaces表示委託類的接口,生成代理類時需要實現這些接口
hInvocationHandler實現類對象,負責連接代理類和委託類的中間類

我們可以這樣理解,如上的動態代理實現實際是雙層的靜態代理,開發者提供了委託類 B,程序動態生成了代理類 A。開發者還需要提供一個實現了InvocationHandler的子類 C,子類 C 連接代理類 A 和委託類 B,它是代理類 A 的委託類,委託類 B 的代理類。用戶直接調用代理類 A 的對象,A 將調用轉發給委託類 C,委託類 C 再將調用轉發給它的委託類 B。

3. 動態代理原理

實際上面最後一段已經說清了動態代理的真正原理。我們來仔細分析下

3.1 生成的動態代理類代碼

下面是上面示例程序運行時自動生成的動態代理類代碼,如何得到這些生成的代碼請見ProxyUtils,查看 class 文件可使用 jd-gui

import com.codekk.java.test.dynamicproxy.Operate;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Operate
{
  private static Method m4;
  private static Method m1;
  private static Method m5;
  private static Method m0;
  private static Method m3;
  private static Method m2;

  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final void operateMethod1()
    throws 
  {
    try
    {
      h.invoke(this, m4, 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)h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void operateMethod2()
    throws 
  {
    try
    {
      h.invoke(this, m5, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void operateMethod3()
    throws 
  {
    try
    {
      h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m4 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod1", new Class[0]);
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m5 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod2", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m3 = Class.forName("com.codekk.java.test.dynamicproxy.Operate").getMethod("operateMethod3", 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());
    }
  }
}

從中我們可以看出動態生成的代理類是以$Proxy爲類名前綴,繼承自Proxy,並且實現了Proxy.newProxyInstance(…)第二個參數傳入的所有接口的類。
如果代理類實現的接口中存在非 public 接口,則其包名爲該接口的包名,否則爲com.sun.proxy
其中的operateMethod1()operateMethod2()operateMethod3()函數都是直接交給h去處理,h在父類Proxy中定義爲

protected InvocationHandler h;

即爲Proxy.newProxyInstance(…)第三個參數。
所以InvocationHandler的子類 C 連接代理類 A 和委託類 B,它是代理類 A 的委託類,委託類 B 的代理類。

3.2. 生成動態代理類原理

以下針對 Java 1.6 源碼進行分析,動態代理類是在調用Proxy.newProxyInstance(…)函數時生成的。

(1). newProxyInstance(…)

函數代碼如下:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    if (h == null) {
        throw new NullPointerException();
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class cl = getProxyClass(loader, interfaces);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        Constructor cons = cl.getConstructor(constructorParams);
        return (Object) cons.newInstance(new Object[] { h });
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    } catch (IllegalAccessException e) {
        throw new InternalError(e.toString());
    } catch (InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        throw new InternalError(e.toString());
    }
}

從中可以看出它先調用getProxyClass(loader, interfaces)得到動態代理類,然後將InvocationHandler作爲代理類構造函數入參新建代理類對象。

(2). getProxyClass(…)

函數代碼及解釋如下(省略了原英文註釋):

/**
 * 得到代理類,不存在則動態生成
 * @param loader 代理類所屬 ClassLoader
 * @param interfaces 代理類需要實現的接口
 * @return
 */
public static Class<?> getProxyClass(ClassLoader loader,
                                     Class<?>... interfaces)
    throws IllegalArgumentException
{
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // 代理類類對象
    Class proxyClass = null;

    /* collect interface names to use as key for proxy class cache */
    String[] interfaceNames = new String[interfaces.length];

    Set interfaceSet = new HashSet();       // for detecting duplicates

    /**
     * 入參 interfaces 檢驗,包含三部分
     * (1)是否在入參指定的 ClassLoader 內
     * (2)是否是 Interface
     * (3)interfaces 中是否有重複
     */
    for (int i = 0; i < interfaces.length; i++) {
        String interfaceName = interfaces[i].getName();
        Class interfaceClass = null;
        try {
            interfaceClass = Class.forName(interfaceName, false, loader);
        } catch (ClassNotFoundException e) {
        }
        if (interfaceClass != interfaces[i]) {
            throw new IllegalArgumentException(
                interfaces[i] + " is not visible from class loader");
        }

        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }

        if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
        }
        interfaceSet.add(interfaceClass);

        interfaceNames[i] = interfaceName;
    }

    // 以接口名對應的 List 作爲緩存的 key
    Object key = Arrays.asList(interfaceNames);

    /*
     * loaderToCache 是個雙層的 Map
     * 第一層 key 爲 ClassLoader,第二層 key 爲 上面的 List,value 爲代理類的弱引用
     */
    Map cache;
    synchronized (loaderToCache) {
        cache = (Map) loaderToCache.get(loader);
        if (cache == null) {
            cache = new HashMap();
            loaderToCache.put(loader, cache);
        }
    }

    /*
     * 以上面的接口名對應的 List 爲 key 查找代理類,如果結果爲:
     * (1) 弱引用,表示代理類已經在緩存中
     * (2) pendingGenerationMarker 對象,表示代理類正在生成中,等待生成完成通知。
     * (3) null 表示不在緩存中且沒有開始生成,添加標記到緩存中,繼續生成代理類
     */
    synchronized (cache) {
        do {
            Object value = cache.get(key);
            if (value instanceof Reference) {
                proxyClass = (Class) ((Reference) value).get();
            }
            if (proxyClass != null) {
                // proxy class already generated: return it
                return proxyClass;
            } else if (value == pendingGenerationMarker) {
                // proxy class being generated: wait for it
                try {
                    cache.wait();
                } catch (InterruptedException e) {
                }
                continue;
            } else {
                cache.put(key, pendingGenerationMarker);
                break;
            }
        } while (true);
    }

    try {
        String proxyPkg = null;     // package to define proxy class in

        /*
         * 如果 interfaces 中存在非 public 的接口,則所有非 public 接口必須在同一包下面,後續生成的代理類也會在該包下面
         */
        for (int i = 0; i < interfaces.length; i++) {
            int flags = interfaces[i].getModifiers();
            if (!Modifier.isPublic(flags)) {
                String name = interfaces[i].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,
            proxyPkg = "";          // use the unnamed package
        }

        {
            // 得到代理類的類名,jdk 1.6 版本中缺少對這個生成類已經存在的處理。
            long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            // 動態生成代理類的字節碼
            // 最終調用 sun.misc.ProxyGenerator.generateClassFile() 得到代理類相關信息寫入 DataOutputStream 實現
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
                // native 層實現,虛擬機加載代理類並返回其類對象
                proxyClass = defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
        // add to set of all generated proxy classes, for isProxyClass
        proxyClasses.put(proxyClass, null);

    } finally {
        // 代理類生成成功則保存到緩存,否則從緩存中刪除,然後通知等待的調用
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
}

函數主要包括三部分:

  • 入參 interfaces 檢驗,包含是否在入參指定的 ClassLoader 內、是否是 Interface、interfaces 中是否有重複
  • 以接口名對應的 List 爲 key 查找代理類,如果結果爲:
    • 弱引用,表示代理類已經在緩存中;
    • pendingGenerationMarker 對象,表示代理類正在生成中,等待生成完成返回;
    • null 表示不在緩存中且沒有開始生成,添加標記到緩存中,繼續生成代理類。
  • 如果代理類不存在調用ProxyGenerator.generateProxyClass(…)生成代理類並存入緩存,通知在等待的緩存。

函數中幾個注意的地方:

  • 代理類的緩存 key 爲接口名對應的 List,接口順序不同表示不同的 key 即不同的代理類。
  • 如果 interfaces 中存在非 public 的接口,則所有非 public 接口必須在同一包下面,後續生成的代理類也會在該包下面。
  • 代理類如果在 ClassLoader 中已經存在的情況沒有做處理。
  • 可以開啓 System Properties 的sun.misc.ProxyGenerator.saveGeneratedFiles開關,保存動態類到目的地址。

Java 1.7 的實現略有不同,通過getProxyClass0(…)函數實現,實現中調用代理類的緩存,判斷代理類在緩存中是否已經存在,存在直接返回,不存在則調用proxyClassCachevalueFactory屬性進行動態生成,valueFactoryapply函數與上面的getProxyClass(…)函數邏輯類似。

4. 使用場景

4.1 J2EE Web 開發中 Spring 的 AOP(面向切面編程) 特性

作用:目標函數之間解耦。
比如在 Dao 中,每次數據庫操作都需要開啓事務,而且在操作的時候需要關注權限。一般寫法是在 Dao 的每個函數中添加相應邏輯,造成代碼冗餘,耦合度高。
使用動態代理前僞代碼如下:

Dao {
    insert() {
        判斷是否有保存的權限;
        開啓事務;
        插入;
        提交事務;
    }

    delete() {
        判斷是否有刪除的權限;
        開啓事務;
        刪除;
        提交事務;
    }
}

使用動態代理的僞代碼如下:

// 使用動態代理,組合每個切面的函數,而每個切面只需要關注自己的邏輯就行,達到減少代碼,鬆耦合的效果
invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
    判斷是否有權限;
    開啓事務;
    Object ob = method.invoke(dao, args);
    提交事務;
    return ob; 
}

4.2 基於 REST 的 Android 端網絡請求框架 Retrofit

作用:簡化網絡請求操作。
一般情況下每個網絡請求我們都需要調用一次HttpURLConnection或者HttpClient進行請求,或者像 Volley 一樣丟進等待隊列中,Retrofit 極大程度簡化了這些操作,示例代碼如下:

public interface GitHubService {
  @GET("/users/{user}/repos")
  List<Repo> listRepos(@Path("user") String user);
}

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("https://api.github.com")
    .build();

GitHubService service = restAdapter.create(GitHubService.class);

以後我們只需要直接調用

List<Repo> repos = service.listRepos("octocat");

即可開始網絡請求,Retrofit的原理就是基於動態代理,它同時用到了 註解 的原理,本文不做深入介紹,具體請等待 Retrofit 源碼解析 完成。

摘自:http://www.codekk.com/open-source-project-analysis/detail/Android/Caij/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8BJava%20%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86

發佈了51 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章