<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
JDK 動態代理的應用還是非常廣泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中動態代理都被大量的使用,可以說學好 JDK 動態代理,對於我們閱讀這些框架的底層原始碼還是很有幫助的
在分析原因之前,我們先完整的看一下實現 JDK 動態代理需要幾個步驟,首先需要定義一個介面
public interface Worker { void work(); }
public class Programmer implements Worker { @Override public void work() { System.out.println("coding..."); } }
自定義一個 Handler,實現 InvocationHandler 介面,通過重寫內部的 invoke() 方法實現邏輯增強
public class WorkHandler implements InvocationHandler { private final Object target; public WorkHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("work")) { System.out.println("before work..."); Object result = method.invoke(target, args); System.out.println("after work..."); return result; } return method.invoke(target, args); } }
在 main() 方法中進行測試,使用 Proxy 類的靜態方法 newProxyInstance() 生成一個代理物件並呼叫方法
public class MainTest { public static void main(String[] args) { Programmer programmer = new Programmer(); Worker worker = (Worker) Proxy.newProxyInstance( programmer.getClass().getClassLoader(), programmer.getClass().getInterfaces(), new WorkHandler(programmer)); worker.work(); } }
before work...
coding...
after work...
既然是一個代理的過程,那麼肯定存在原生物件和代理物件之分,下面我們檢視原始碼中是如何動態的建立代理物件的過程。
上面例子中,建立代理物件呼叫的是 Proxy 類的靜態方法 newProxyInstance(),檢視一下原始碼
public class Proxy implements java.io.Serializable { protected InvocationHandler h; // 有參構造器,引數是 InvocationHandler protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 如果h為空直接丟擲空指標異常,之後所有的單純的判斷null並拋異常,都是此方法 Objects.requireNonNull(h); // 拷貝類實現的所有介面 final Class<?>[] intfs = interfaces.clone(); // 獲取當前系統安全介面 final SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Reflection.getCallerClass 返回撥用該方法的方法的呼叫類;loader:介面的類載入器 // 進行包存取許可權、類載入器許可權等檢查 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } // 查詢或生成指定的代理類 Class<?> cl = getProxyClass0(loader, intfs); // 用指定的呼叫處理程式呼叫它的建構函式 try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // 獲取代理類別建構函式物件 // constructorParams是類常數,作為代理類建構函式的引數型別,常數定義如下: // private static final Class<?>[] constructorParams = { InvocationHandler.class }; final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } // 根據代理類別建構函式物件來建立需要返回的代理類物件 return cons.newInstance(new Object[]{h}); } // 省略 catch...... } }
上面這個過程中,獲取構造方法和生成代理物件都是利用的 Java 中的反射機制,而需要重點看的是生成代理類的方法 getProxyClass0()
3.1.1. getProxyClass0() 方法
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { // 介面數不得超過 65535 個,這麼大,足夠使用的了 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // 如果快取中有代理類了直接返回,否則將由代理類工廠ProxyClassFactory建立代理類 return proxyClassCache.get(loader, interfaces); }
如果快取中已經存在了就直接從快取中取,這裡的 proxyClassCache 是一個 WeakCache 型別,如果快取中目標 classLoader 和介面陣列對應的類已經存在,那麼返回快取的副本。如果沒有就使用 ProxyClassFactory 去生成 Class 物件
3.1.1.1. get() 方法
// key:類載入器;parameter:介面陣列 public V get(K key, P parameter) { // 檢查指定型別的物件參照不為空null。當引數為null時,丟擲空指標異常 Objects.requireNonNull(parameter); // 清除已經被 GC 回收的弱參照 expungeStaleEntries(); // 將ClassLoader包裝成CacheKey, 作為一級快取的key Object cacheKey = CacheKey.valueOf(key, refQueue); // 獲取得到二級快取 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; } } // 根據代理類實現的介面陣列來生成二級快取key Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // 通過subKey獲取二級快取值 Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; // 這個迴圈提供了輪詢機制, 如果條件為假就繼續重試直到條件為真為止 while (true) { if (supplier != null) { // 在這裡supplier可能是一個Factory也可能會是一個CacheValue // 在這裡不作判斷, 而是在Supplier實現類的get方法裡面進行驗證 V value = supplier.get(); if (value != null) { return value; } } if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { // 到這裡表明subKey沒有對應的值, 就將factory作為subKey的值放入 supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { supplier = factory; } // 否則, 可能期間有其他執行緒修改了值, 那麼就不再繼續給subKey賦值, 而是取出來直接用 } else { // 期間可能其他執行緒修改了值, 那麼就將原先的值替換 if (valuesMap.replace(subKey, supplier, factory)) { supplier = factory; } else { supplier = valuesMap.get(subKey); } } } }
很明顯,重點關注下面程式碼
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
3.1.1.1.1. apply() 方法
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 代理類的字首名都以 $Proxy 開始 private static final String proxyClassNamePrefix = "$Proxy"; // 使用唯一的編號給作為代理類名的一部分,如 $Proxy0,$Proxy1 等 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { // 驗證指定的類載入器(loader)載入介面所得到的Class物件(interfaceClass)是否與intf物件相同 Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } // 驗證該Class物件是不是介面 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; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; // 驗證所有非公共的介面在同一個包內;公共的就無需處理 for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; 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"); } } } // 1.根據規則生成檔名 if (proxyPkg == null) { /*如果都是public介面,那麼生成的代理類就在com.sun.proxy包下如果報 java.io.FileNotFoundException: comsunproxy$Proxy0.class (系統找不到指定的路徑。)的錯誤,就先在你專案中建立com.sun.proxy路徑*/ proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } long num = nextUniqueNumber.getAndIncrement(); // 代理類的完全限定類名,如 com.sun.proxy.$Proxy0.calss String proxyName = proxyPkg + proxyClassNamePrefix + num; // 2.生成代理的位元組碼陣列 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); // 3.生成 Class try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
在 apply() 方法中,主要做了下面 3 件事
3.1.2. getConstructor() 和 newInstance() 方法
返回代理類的 Class 後的流程,獲取構造方法和生成代理物件都是利用的 Java 中的反射機制
建立代理物件流程的原始碼分析完了,我們可以先通過 debug 來看看上面生成的這個代理物件究竟是個什麼
和原始碼中看到的規則一樣,是一個 Class 為 $Proxy0 的物件。再看一下代理物件的 Class 的詳細資訊
類的全限定類名是 com.sun.proxy.$Proxy0,在上面我們提到過,這個類是在執行過程中動態生成的
看一下反編譯後 $Proxy0.java 檔案的內容,下面的程式碼中,我只保留了核心部分,省略了無關緊要的 equals()、toString()、hashCode() 方法的定義
public final class $Proxy0 extends Proxy implements Worker{ public $Proxy0(InvocationHandler invocationhandler){ super(invocationhandler); } public final void work(){ try{ super.h.invoke(this, m3, null); return; }catch(Error _ex) { } catch(Throwable throwable){ throw new UndeclaredThrowableException(throwable); } } private static Method m3; static { try{ m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]); //省略其他Method }//省略catch } }
這個臨時生成的代理類 $Proxy0 中主要做了下面的幾件事
到這裡,整體的流程就分析完了,我們可以用一張圖來簡要總結上面的過程
其實如果不看上面的分析,我們也應該知道,要擴充套件一個類有常見的兩種方式,繼承父類別或實現介面。這兩種方式都允許我們對方法的邏輯進行增強,但現在不是由我們自己來重寫方法,而是要想辦法讓 JVM 去呼叫 InvocationHandler 中的 invoke() 方法,也就是說代理類需要和兩個東西關聯在一起
而 JDK 動態代理處理這個問題的方式是選擇繼承父類別 Proxy,並把 InvocationHandler 儲存在父類別的物件中
public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } // ...... }
通過父類別 Proxy 的構造方法,儲存了建立代理物件過程中傳進來的 InvocationHandler 的範例,使用 protected 修飾保證了它可以在子類中被存取和使用。
但是同時,因為 Java 是單繼承的,因此在代理類 $Proxy0 繼承了 Proxy 後,其只能通過實現目標介面的方式來實現方法的擴充套件,達到我們增強目標方法邏輯的目的
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45