你好呀,我是歪歪。
上週釋出了《我試圖通過這篇文章告訴你,這行原始碼有多牛逼。》這篇文章。
文章中有這樣的一段描述:
然後有個讀者來問我:
是怎麼把 JDK 原始碼中的一行程式碼給註釋掉的?
這個問題確實不錯,屬於一個偶爾用一下能起到奇效的原始碼偵錯技巧。所以我決定寫個文章來說明一下這個問題。
但是這個技巧確實非常的簡單,簡單到一句話就能說明白,所以正如標題說到的「短小精悍,簡單粗暴,但足夠好用」,這篇文章也會非常的短。
首先,把問題換個問法,既然我能把原始碼註釋了,那說明我能修改原始碼。所以,問題就變成了:我怎麼去修改 JDK 的原始碼呢?
這個問題有很多個回答,但是我這裡的回答很簡單。把原始碼拷貝一份出來,原模原樣的放一份到自己的專案中即可。
就像是這樣:
然後你在使用的時候,直接用你 CV 過來的原始碼,就行了:
但是我一般使用這個方法的時候,CV 過來時,會把類名稱重新命名一下,以示區分,其他的啥都不改。
反正不管怎麼樣吧,這樣在你的專案裡面有一份「原始碼」了,這個「原始碼」和 JDK 裡面的原始碼一模一樣,這樣你就能隨便進行修改了。
比如,我在呼叫 put 方法的時候,加一點紀錄檔輸出:
這樣測試用例跑起來的時候,就能直接輸出你新增的內容:
你都能新增程式碼了,註釋程式碼,甚至是修改程式碼邏輯,那還不是手到擒來的事情嗎?
對於一些比較複雜的場景,比如非同步或者回圈等等場景,當你想要在原始碼中加入輸出語句方便進行學習和偵錯的時候,你就可以用到這招。
這就是我這篇文章要教你的一個關於 JDK 原始碼的偵錯技巧。
整體用處不大,但是當你能想到用它的時候,就是發揮奇效的時候。
既然話題都到這裡了,那麼我再給你補充一個關於第三方框架的類似的偵錯技巧。
還是先舉個例子。
比如我在專案中使用到了 @Async 註解,然後有一個自定義執行緒池,發起一個請求之後可以看到確實是使用了我的自定義執行緒池:
然後,問題就來了。
假設,我想讓 @Async 註解支援 EL 表示式,也就是這樣的寫法:
目前,Spring 是不支援這樣的設定的,當你這樣設定並行起呼叫,會丟擲這樣的一個異常:
它會把 ${thread-pool.name} 認為是一個 Bean,然後 Spring 裡面並沒有這樣的一個 Bean,所以丟擲找不到 Bean 的異常。
那麼怎麼才能讓 @Async 註解支援 EL 表示式呢?
我之前寫過《舒服,給Spring貢獻一波原始碼。》這篇文章,裡面用的就是這個案例,有興趣的話可以去看看,我就不展開說了。
在文章裡面,經過分析,我們知道只需要在 org.springframework.aop.interceptor.AsyncExecutionAspectSupport.findQualifiedExecutor(BeanFactory,String) 這個方法中,加入這幾行程式碼就行了:
if (beanFactory instanceof ConfigurableBeanFactory) {
EmbeddedValueResolver embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory)beanFactory);
qualifier = embeddedValueResolver.resolveStringValue(qualifier);
}
但是我當時採取的方案是通過 idea 的 Evaluate Expression 功能:
經過評論區提醒,其實用 CV 大法,更加直接、方便。
同樣的道理,直接把 AsyncExecutionAspectSupport 這個類粘到我們自己的專案中去:
這裡需要注意的是,要保證包名稱也一模一樣,因為這個方法的底層邏輯是基於類載入機制實現的。
這樣,我們就能針對我們自己專案中的 AsyncExecutionAspectSupport 類進行修改:
再次發起呼叫,這事兒就算成了:
這個方法,適用於任何你能拿到原始碼的任何第三方框架。
雖然,很多第三方框架裡面都會主動留下足夠多的擴充套件點,以便使用者進行客製化化開發。
所以我提供的這個方法好像用處並不是很大,但是我當年看 Dubbo 原始碼的時候,就是這樣的看的。
就像是這樣,在原始碼裡面加入了大量的輸出語句,然後基於輸出語句去做分析:
雖然現在想起來,更加正確的操作應該是基於它的 SPI 機制去做。
但是,管它呢,反正當時我就是靠這種歪門邪道,也看的明明白白的。
好了,以上就本文的全部內容。
突出的就是一個短小精悍,簡單粗暴,又足夠好用。
玩去吧。