日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用反射前先三思

發布時間:2023/12/3 编程问答 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用反射前先三思 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

介紹

有時,作為開發人員,您可能會遇到無法使用new運算符實例化對象的情況,因為其類名存儲在配置XML中的某個位置,或者您需要調用一個名稱指定為注釋屬性的方法。 在這種情況下,您總會有一個答案:“使用反射!”。

在新版本的CUBA框架中 ,我們決定改進體系結構的許多方面,最重要的變化之一是在控制器UI中棄用了“經典”事件偵聽器。 在該框架的先前版本中,屏幕的init()方法中注冊了許多樣板代碼的偵聽器,使您的代碼幾乎不可讀,因此新概念應該可以解決此問題。

您始終可以通過為帶注釋的方法存儲java.lang.reflect.Method實例來實現方法偵聽器,并像在許多框架中實現的那樣調用它們,但是我們決定看看其他選項。 反射調用需要付出一定的成本,如果您開發了生產級框架,則即使是很小的改進也可能在短時間內得到回報。

在本文中,我們將介紹反射API的用法和優缺點,并查看其他替代反射API調用的選項-AOT和代碼生成以及LambdaMetafactory。

反射–良好的舊可靠API

根據維基百科,“反射是計算機程序在運行時檢查,自省和修改其自身的結構和行為的能力”。

對于大多數Java開發人員而言,反射并不是新事物,它在許多情況下都被使用。 我敢說Java在沒有反思的情況下不會變成現在的樣子。 只需考慮批注處理,數據序列化,通過批注或配置文件進行方法綁定…對于最流行的IoC框架,反射API是基石,因為廣泛使用了類代理,方法引用用法等。此外,您還可以添加面向方面的方法編程到此列表–一些AOP框架依靠反射來進行方法執行攔截。

反射有什么問題嗎? 我們可以考慮其中的三個:

速度 –反射呼叫比直接呼叫慢。 我們可以看到每個JVM版本在反射API性能上都有很大的改進,JIT編譯器的優化算法越來越好,但是反射方法調用的速度仍然比直接方法慢三倍。

類型安全性 –如果在代碼中使用方法引用,則它只是方法引用。 如果編寫的代碼通過其引用調用方法并傳遞錯誤的參數,則該調用將在運行時失敗,而不是在編譯時或加載時失敗。

可追溯性 –如果一個反射方法調用失敗,那么找到導致這一問題的代碼行可能會很棘手,因為堆棧跟蹤通常很龐大。 您需要深入研究所有這些invoke()和proxy()調用。

但是,如果您研究Spring中的事件偵聽器實現或Hibernate中的JPA回調,則會在其中看到熟悉的java.lang.reflect.Method引用。 而且我懷疑它是否會在不久的將來更改–成熟的框架又大又復雜,已在許多關鍵任務系統中使用,因此開發人員應謹慎地進行重大更改。

讓我們看看其他選項。

AOT編譯和代碼生成–使應用程序再次快速

反射替換的第一個候選人–代碼生成。 如今,我們可以看到諸如Micronaut和Quarkus之類的新框架的興起,它們針對兩個目標:快速啟動時間和低內存占用。 在微服務和無服務器應用程序時代,這兩個指標至關重要。 最近的框架正試圖通過使用提前編譯和代碼生成來完全擺脫反思。 通過使用注釋處理,類型訪問者和其他技術,他們將直接方法調用,對象實例化等添加到代碼中,從而使應用程序更快。 那些不會在啟動期間使用Class.newInstance()創建和注入bean的Class.newInstance() ,不會在偵聽器中使用反射方法的調用,等等。看起來很有希望,但是這里有什么取舍嗎? 答案是–是的。

第一個–您運行的代碼不完全是您自己的。 代碼生成會更改原始代碼,因此,如果出現問題,您將無法確定是您的錯誤還是代碼處理算法中的故障。 并且不要忘記,現在您應該調試生成的代碼,而不是代碼。

第二個權衡–您必須使用供應商提供的單獨工具/插件才能使用該框架。 您不能“只是”運行代碼,而應以特殊方式對其進行預處理。 并且,如果您在生產中使用框架,則應將供應商的錯誤修正應用于框架代碼庫和代碼處理工具。

代碼生成早已為人所知,但Micronaut或Quarkus尚未出現。 例如,在CUBA中,我們在編譯期間使用自定義的Grails插件和Javassist庫使用類增強功能。 我們添加了額外的代碼來生成實體更新事件,并在類代碼中將bean驗證消息作為String字段包含在類代碼中,以用于漂亮的UI表示。

但是為事件偵聽器實現代碼生成看起來有些極端,因為這將需要對內部體系結構進行徹底的更改。 有反射這樣的東西,但是更快嗎?

LambdaMetafactory –更快的方法調用

在Java 7中,引入了新的JVM指令invokedynamic 。 最初針對基于JVM的動態語言實現,它已成為API調用的良好替代。 該API可以使我們在性能上優于傳統反射。 還有一些特殊的類可以在Java代碼中構造invokedynamic調用:

  • MethodHandle –該類是Java 7中引入的,但它仍然不為人所知。
  • LambdaMetafactory –是Java 8中引入的。它是動態調用思想的進一步發展。 該API基于MethodHandle。

方法句柄API可以很好地替代標準反射,因為JVM在MethodHandle創建期間僅執行一次所有預調用檢查。 長話短說–方法句柄是對基礎方法,構造函數,字段或類似的低級操作的類型化,直接可執行的引用,具有參數或返回值的可選轉換。

出乎意料的是,與按反射API相比,純MethodHandle引用調用不會提供更好的性能,除非您按照本電子郵件列表中所述將MethodHandle引用設為靜態。

但是LambdaMetafactory是另一個故事–它允許我們在運行時中生成功能接口的實例,該實例包含對由MethodHandle解析的方法的MethodHandle 。 使用此lambda對象,我們可以直接調用引用的方法。 這是一個例子:

private BiConsumer createVoidHandlerLambda(Object bean, Method method) throws Throwable { MethodHandles.Lookup caller = MethodHandles.lookup(); CallSite site = LambdaMetafactory.metafactory(caller, "accept" , MethodType.methodType(BiConsumer. class ), MethodType.methodType( void . class , Object. class , Object. class ), caller.findVirtual(bean.getClass(), method.getName(), MethodType.methodType( void . class , method.getParameterTypes()[ 0 ])), MethodType.methodType( void . class , bean.getClass(), method.getParameterTypes()[ 0 ])); MethodHandle factory = site.getTarget(); BiConsumer listenerMethod = (BiConsumer) factory.invoke(); return listenerMethod; }

請注意,通過這種方法,我們可以只使用java.util.function.BiConsumer而不是java.lang.reflect.Method ,因此不需要太多的重構。 讓我們考慮一下事件偵聽器處理程序代碼–它是對Spring Framework的簡化改編:

public class ApplicationListenerMethodAdapter implements GenericApplicationListener { private final Method method; public void onApplicationEvent(ApplicationEvent event) { Object bean = getTargetBean(); Object result = this .method.invoke(bean, event); handleResult(result); } }

這就是可以使用基于Lambda的方法參考進行更改的方式:

public class ApplicationListenerLambdaAdapter extends ApplicationListenerMethodAdapter { private final BiFunction funHandler; public void onApplicationEvent(ApplicationEvent event) { Object bean = getTargetBean(); Object result = handler.apply(bean, event); handleResult(result); } }

該代碼具有細微的變化,并且功能相同。 但是與傳統反射相比,它具有一些優勢:

類型安全性 –您在LambdaMetafactory.metafactory調用中指定方法簽名,因此您將無法將“公正”方法綁定為事件偵聽器。

可追溯性 – lambda包裝器僅對方法調用堆棧跟蹤添加了一個額外的調用。 它使調試更加容易。

速度 –這是應該衡量的事情。

標桿管理

對于新版本的CUBA框架,我們創建了一個基于JMH的微基準,以比較“傳統”反射方法調用(基于lambda)的執行時間和吞吐量,并添加了直接方法調用以進行比較。 在測試執行之前,已創建并緩存了方法引用和lambda。

我們使用了以下基準測試參數:

@BenchmarkMode ({Mode.Throughput, Mode.AverageTime}) @Warmup (iterations = 5 , time = 1000 , timeUnit = TimeUnit.MILLISECONDS) @Measurement (iterations = 10 , time = 1000 , timeUnit = TimeUnit.MILLISECONDS)

您可以從GitHub下載基準測試,然后自己運行測試。

對于JVM 11.0.2和JMH 1.21,我們得到以下結果(運行次數可能略有不同):

測試-獲得價值 吞吐量(運營/我們) 執行時間(我們/運營)
LambdaGetTest 72 0.0118
ReflectionGetTest 65歲 0.0177
DirectMethodGetTest 260 0.0048
測試–設定值 吞吐量(運營/我們) 執行時間(我們/運營)
LambdaSetTest 96 0.0092
ReflectionSetTest 58 0.0173
DirectMethodSetTest 415 0.0031

如您所見,基于lambda的方法處理程序平均快30%。 關于基于lambda的方法調用性能, 這里有很好的討論。 結果-LambdaMetafactory生成的類可以被內聯,從而獲得一些性能改進。 它比反射更快,因為反射調用必須在每次調用時通過安全檢查。

該基準測試是很貧乏的,沒有考慮類的層次結構,最終方法等,它只測量“公正”的方法調用,但這足以滿足我們的目的。

實作

在CUBA中,您可以使用@Subscribe注釋使方法“監聽”各種特定于CUBA的應用程序事件。 在內部,我們使用此新的基于MethodHandles / LambdaMetafactory的API來加快偵聽器的調用。 第一次調用后將緩存所有方法句柄。

新的體系結構使代碼更簡潔,更易于管理,尤其是在具有大量事件處理程序的復雜UI的情況下。 看看簡單的例子。 假設您需要根據添加到此訂單的產品重新計算訂單金額。 您有一個calculateAmount()方法,您需要在訂單中的一組產品更改后立即調用它。 這是UI控制器的舊版本:

LambdaGetTest

在新版本中的外觀如下:

LambdaGetTest

代碼更簡潔,我們能夠擺脫通常用事件處理程序創建語句填充的“魔術” init()方法。 而且,我們甚至不需要將數據組件注入控制器中-框架將通過組件ID找到它。

結論

盡管最近引入了新一代框架( Micronaut , Quarkus ),它們比“傳統”框架具有一些優勢,但是由于Spring的支持 ,仍然存在大量基于反射的代碼。 我們將看到市場在不久的將來將如何變化,但是如今,Spring在Java應用程序框架中是顯而易見的領導者,因此,我們將使用反射API已有相當長的時間。

而且,如果您考慮在代碼中使用反射API,無論是實現自己的框架還是僅是應用程序,請考慮另外兩個選項–代碼生成,尤其是LambdaMetafactory。 后者將提高代碼執行速度,而與“傳統”反射API相比,開發不會花費更多時間。

翻譯自: https://www.javacodegeeks.com/2019/09/think-twice-before-using-reflection.html

總結

以上是生活随笔為你收集整理的使用反射前先三思的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。