標籤:
Java 動態代理
本文為 Android 開源項目源碼解析 公用技術點中的 動態代理 部分
項目地址:Jave Proxy,分析的版本:openjdk 1.6,Demo 地址:Proxy Demo
分析者:Caij,校對者:Trinea,校對狀態:完成
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相應函數,並且隱藏了Class的operateMethod3()函數。
靜態代理中代理類和委託類也常常繼承同一父類或實現同一介面。
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表示代理對象被調用的函數的參數。
調用代理對象的每個函數實際最終都是調用了InvocationHandler的invoke函數。這裡我們在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(…)函數建立了一個代理對象,實際代理類就是在這時候動態產生的。我們調用該代理對象的函數就會調用到timingInvocationHandler的invoke函數(是不是有點類似靜態代理),而invoke函數實現中調用委託類對象new OperateImpl()相應的 method(是不是有點類似靜態代理)。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
loader表示類載入器
interfaces表示委託類的介面,組建代理程式類時需要實現這些介面
h是InvocationHandler實作類別對象,負責串連代理類和委託類的中間類
我們可以這樣理解,如上的動態代理實現實際是雙層的靜態代理,開發人員提供了委託類 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(…)函數實現,實現中調用代理類的緩衝,判斷代理類在緩衝中是否已經存在,存在直接返回,不存在則調用proxyClassCache的valueFactory屬性進行動態產生,valueFactory的apply函數與上面的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 源碼解析 完成。
【轉載】Java 動態代理