Java動態(tài)代理

  1. 相關(guān)概念
    1.1 代理
    ??在某些情況下,我們不希望或是不能直接訪問對象 A,而是通過訪問一個中介對象 B,由 B 去訪問 A 達成目的,這種方式我們就稱為代理。
    ??這里對象 A 所屬類我們稱為委托類,也稱為被代理類,對象 B 所屬類稱為代理類。代理優(yōu)點有:
  • 隱藏委托類的實現(xiàn)
  • 解耦,不改變委托類代碼情況下做一些額外處理,比如添加初始判斷及其他公共操作

根據(jù)程序運行前代理類是否已經(jīng)存在,可以將代理分為靜態(tài)代理和動態(tài)代理。
1.2 靜態(tài)代理
??代理類在程序運行前已經(jīng)存在的代理方式稱為靜態(tài)代理。通過上面解釋可以知道,由開發(fā)人員編寫或是編譯器生成代理類的方式都屬于靜態(tài)代理,如下是簡單的靜態(tài)代理實例:

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中的函數(shù)都是直接調(diào)用ClassA相應(yīng)函數(shù),并且隱藏了Class的operateMethod3()函數(shù)。
??靜態(tài)代理中代理類和委托類也常常繼承同一父類或?qū)崿F(xiàn)同一接口。
1.3 動態(tài)代理
??代理類在程序運行前不存在、運行時由程序動態(tài)生成的代理方式稱為動態(tài)代理。
??Java 提供了動態(tài)代理的實現(xiàn)方式,可以在運行時刻動態(tài)生成代理類。這種代理方式的一大好處是可以方便對代理類的函數(shù)做統(tǒng)一或特殊處理,如記錄所有函數(shù)執(zhí)行時間、所有函數(shù)執(zhí)行前添加驗證判斷、對某個特殊函數(shù)進行特殊操作,而不用像靜態(tài)代理方式那樣需要修改每個函數(shù)。

  1. 動態(tài)代理實例
    實現(xiàn)動態(tài)代理包括三步:
    (1). 新建委托類;
    (2). 實現(xiàn)InvocationHandler接口,這是負責連接代理類和委托類的中間類必須實現(xiàn)的接口;
    (3). 通過Proxy類新建代理類對象。

下面通過實例具體介紹,假如現(xiàn)在我們想統(tǒng)計某個類所有函數(shù)的執(zhí)行時間,傳統(tǒng)的方式是在類的每個函數(shù)前打點統(tǒng)計,動態(tài)代理方式如下:
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是一個接口,定了了一些函數(shù),我們要統(tǒng)計這些函數(shù)的執(zhí)行時間。
  • OperateImpl是委托類,實現(xiàn)Operate接口。每個函數(shù)簡單輸出字符串,并等待一段時間。動態(tài)代理要求委托類必須實現(xiàn)了某個接口,比如這里委托類OperateImpl實現(xiàn)了Operate,原因會后續(xù)公布。
    2.2. 實現(xiàn) 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是負責連接代理類和委托類的中間類必須實現(xiàn)的接口。其中只有一個
public Object invoke(Object proxy, Method method, Object[] args)

函數(shù)需要去實現(xiàn),參數(shù):

  • proxy表示下面2.3 通過Proxy.newProxyInstance() 生成的代理類對象
  • method表示代理對象被調(diào)用的函數(shù)。
  • args表示代理對象被調(diào)用的函數(shù)的參數(shù)。

調(diào)用代理對象的每個函數(shù)實際最終都是調(diào)用了InvocationHandler的invoke函數(shù)。這里我們在invoke實現(xiàn)中添加了開始結(jié)束計時,其中還調(diào)用了委托類對象target的相應(yīng)函數(shù),這樣便完成了統(tǒng)計執(zhí)行時間的需求。invoke函數(shù)中我們也可以通過對method做一些判斷,從而對某些函數(shù)特殊處理。
2.3. 通過 Proxy 類靜態(tài)函數(shù)生成代理對象

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構(gòu)造函數(shù)入?yún)?chuàng)建timingInvocationHandler對象;然后通過Proxy.newProxyInstance(…)函數(shù)新建了一個代理對象,實際代理類就是在這時候動態(tài)生成的。我們調(diào)用該代理對象的函數(shù)就會調(diào)用到timingInvocationHandler的invoke函數(shù)(是不是有點類似靜態(tài)代理),而invoke函數(shù)實現(xiàn)中調(diào)用委托類對象new OperateImpl()相應(yīng)的 method(是不是有點類似靜態(tài)代理)。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • loader表示類加載器interfaces表示委托類的接口,生成代理類時需要實現(xiàn)這些接口h是InvocationHandler實現(xiàn)類對象,負責連接代理類和委托類的中間類我們可以這樣理解,如上的動態(tài)代理實現(xiàn)實際是雙層的靜態(tài)代理,開發(fā)者提供了委托類 B,程序動態(tài)生成了代理類 A。
  • 開發(fā)者還需要提供一個實現(xiàn)了InvocationHandler的子類 C,子類 C 連接代理類 A 和委托類 B,它是代理類 A 的委托類,委托類 B 的代理類。用戶直接調(diào)用代理類 A 的對象,A 將調(diào)用轉(zhuǎn)發(fā)給委托類 C,委托類 C 再將調(diào)用轉(zhuǎn)發(fā)給它的委托類 B。
  1. 動態(tài)代理原理
    實際上面最后一段已經(jīng)說清了動態(tài)代理的真正原理。我們來仔細分析下
    3.1 生成的動態(tài)代理類代碼
    下面是上面示例程序運行時自動生成的動態(tài)代理類代碼,如何得到這些生成的代碼請見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());
    }
  }
}

從中我們可以看出動態(tài)生成的代理類是以$Proxy為類名前綴,繼承自Proxy,并且實現(xiàn)了Proxy.newProxyInstance(…)第二個參數(shù)傳入的所有接口的類。如果代理類實現(xiàn)的接口中存在非 public 接口,則其包名為該接口的包名,否則為com.sun.proxy。其中的operateMethod1()、operateMethod2()、operateMethod3()函數(shù)都是直接交給h去處理,h在父類Proxy中定義為
protected InvocationHandler h;即為Proxy.newProxyInstance(…)第三個參數(shù)。所以InvocationHandler的子類 C 連接代理類 A 和委托類 B,它是代理類 A 的委托類,委托類 B 的代理類。
3.2. 生成動態(tài)代理類原理
以下針對 Java 1.6 源碼進行分析,動態(tài)代理類是在調(diào)用Proxy.newProxyInstance(…)函數(shù)時生成的。
(1). newProxyInstance(…)
函數(shù)代碼如下:

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());
    }
}

從中可以看出它先調(diào)用getProxyClass(loader, interfaces)得到動態(tài)代理類,然后將InvocationHandler作為代理類構(gòu)造函數(shù)入?yún)⑿陆ù眍悓ο蟆?br> (2). getProxyClass(…)
函數(shù)代碼及解釋如下(省略了原英文注釋):

/**
 * 得到代理類,不存在則動態(tài)生成
 * @param loader 代理類所屬 ClassLoader
 * @param interfaces 代理類需要實現(xiàn)的接口
 * @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

    /**
     * 入?yún)?interfaces 檢驗,包含三部分
     * (1)是否在入?yún)⒅付ǖ?ClassLoader 內(nèi)
     * (2)是否是 Interface
     * (3)interfaces 中是否有重復(fù)
     */
    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;
    }

    // 以接口名對應(yīng)的 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);
        }
    }

    /*
     * 以上面的接口名對應(yīng)的 List 為 key 查找代理類,如果結(jié)果為:
     * (1) 弱引用,表示代理類已經(jīng)在緩存中
     * (2) pendingGenerationMarker 對象,表示代理類正在生成中,等待生成完成通知。
     * (3) null 表示不在緩存中且沒有開始生成,添加標記到緩存中,繼續(xù)生成代理類
     */
    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 接口必須在同一包下面,后續(xù)生成的代理類也會在該包下面
         */
        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 版本中缺少對這個生成類已經(jīng)存在的處理。
            long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            // 動態(tài)生成代理類的字節(jié)碼
            // 最終調(diào)用 sun.misc.ProxyGenerator.generateClassFile() 得到代理類相關(guān)信息寫入 DataOutputStream 實現(xiàn)
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
                // native 層實現(xiàn),虛擬機加載代理類并返回其類對象
                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 {
        // 代理類生成成功則保存到緩存,否則從緩存中刪除,然后通知等待的調(diào)用
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
}

函數(shù)主要包括三部分:
入?yún)?interfaces 檢驗,包含是否在入?yún)⒅付ǖ?ClassLoader 內(nèi)、是否是 Interface、interfaces 中是否有重復(fù)以接口名對應(yīng)的 List 為 key 查找代理類,如果結(jié)果為:弱引用,表示代理類已經(jīng)在緩存中;pendingGenerationMarker 對象,表示代理類正在生成中,等待生成完成返回;null 表示不在緩存中且沒有開始生成,添加標記到緩存中,繼續(xù)生成代理類。
如果代理類不存在調(diào)用ProxyGenerator.generateProxyClass(…)
生成代理類并存入緩存,通知在等待的緩存。

函數(shù)中幾個注意的地方:
代理類的緩存 key 為接口名對應(yīng)的 List,接口順序不同表示不同的 key 即不同的代理類。
如果 interfaces 中存在非 public 的接口,則所有非 public 接口必須在同一包下面,后續(xù)生成的代理類也會在該包下面。
代理類如果在 ClassLoader 中已經(jīng)存在的情況沒有做處理??梢蚤_啟 System Properties 的sun.misc.ProxyGenerator.saveGeneratedFiles
開關(guān),保存動態(tài)類到目的地址。

Java 1.7 的實現(xiàn)略有不同,通過etProxyClass0(…)
函數(shù)實現(xiàn),實現(xiàn)中調(diào)用代理類的緩存,判斷代理類在緩存中是否已經(jīng)存在,存在直接返回,不存在則調(diào)用proxyClassCache的valueFactory屬性進行動態(tài)生成,valueFactory的apply函數(shù)與上面的getProxyClass(…)函數(shù)邏輯類似。

  1. 使用場景
    4.1 J2EE Web 開發(fā)中 Spring 的 AOP(面向切面編程) 特性
    作用:目標函數(shù)之間解耦。比如在 Dao 中,每次數(shù)據(jù)庫操作都需要開啟事務(wù),而且在操作的時候需要關(guān)注權(quán)限。一般寫法是在 Dao 的每個函數(shù)中添加相應(yīng)邏輯,造成代碼冗余,耦合度高。使用動態(tài)代理前偽代碼如下:
Dao {
    insert() {
        判斷是否有保存的權(quán)限;
        開啟事務(wù);
        插入;
        提交事務(wù);
    }

    delete() {
        判斷是否有刪除的權(quán)限;
        開啟事務(wù);
        刪除;
        提交事務(wù);
    }
}

使用動態(tài)代理的偽代碼如下:

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

4.2 基于 REST 的 Android 端網(wǎng)絡(luò)請求框架 Retrofit
作用:簡化網(wǎng)絡(luò)請求操作。一般情況下每個網(wǎng)絡(luò)請求我們都需要調(diào)用一次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);

以后我們只需要直接調(diào)用

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

即可開始網(wǎng)絡(luò)請求,Retrofit的原理就是基于動態(tài)代理,它同時用到了 注解 的原理,本文不做深入介紹,具體請等待 Retrofit 源碼解析 完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容