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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

三思笔记_使用反射前先三思

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

三思筆記

介紹

有時,作為開發(fā)人員,您可能會遇到無法使用new運(yùn)算符實(shí)例化對象的情況,因?yàn)槠漕惷Q存儲在配置XML中的某個位置,或者您需要調(diào)用一個名稱指定為注釋屬性的方法。 在這種情況下,您總會有一個答案:“使用反射!”。

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

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

在本文中,我們將介紹反射API的用法,優(yōu)缺點(diǎn),并查看其他替代反射API調(diào)用的選項(xiàng)-AOT和代碼生成以及LambdaMetafactory。

反射–良好的舊可靠API

根據(jù)維基百科,“反射是計(jì)算機(jī)程序在運(yùn)行時檢查,自省和修改其自身的結(jié)構(gòu)和行為的能力”。

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

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

速度 –反射呼叫比直接呼叫慢。 我們可以看到,隨著每個JVM版本的發(fā)布,反射API的性能都有了很大的提高,JIT編譯器的優(yōu)化算法越來越好,但是反射方法的調(diào)用速度仍然比直接調(diào)用慢3倍。

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

可追溯性 –如果反射方法調(diào)用失敗,則可能很難找到導(dǎo)致這一問題的代碼行,因?yàn)槎褩8櫷ǔ:荦嫶蟆?您需要深入研究所有這些invoke()和proxy()調(diào)用。

但是,如果您研究Spring中的事件偵聽器實(shí)現(xiàn)或Hibernate中的JPA回調(diào),則會在其中看到熟悉的java.lang.reflect.Method引用。 而且我懷疑它是否會在不久的將來更改–成熟的框架又大又復(fù)雜,用在許多關(guān)鍵任務(wù)系統(tǒng)中,因此開發(fā)人員應(yīng)該謹(jǐn)慎地進(jìn)行重大更改。

讓我們看看其他選項(xiàng)。

AOT編譯和代碼生成–使應(yīng)用程序再次快速

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

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

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

代碼生成早已為人所知,但Micronaut或Quarkus卻沒有出現(xiàn)。 例如,在CUBA中,我們使用自定義Grails插件和Javassist庫在編譯期間使用類增強(qiáng)。 我們添加了額外的代碼來生成實(shí)體更新事件,并將bean驗(yàn)證消息作為String字段包含在類代碼中,以用于漂亮的UI表示形式。

但是為事件偵聽器實(shí)現(xiàn)代碼生成看起來有些極端,因?yàn)檫@將需要對內(nèi)部體系結(jié)構(gòu)進(jìn)行徹底的更改。 有反射這樣的東西,但是更快嗎?

LambdaMetafactory –更快的方法調(diào)用

在Java 7中,引入了新的JVM指令invokedynamic 。 最初針對基于JVM的動態(tài)語言實(shí)現(xiàn),它已成為API調(diào)用的良好替代。 該API可以使我們在性能上優(yōu)于傳統(tǒng)反射。 還有一些特殊的類可以在Java代碼中構(gòu)造invokedynamic調(diào)用:

  • MethodHandle –此類是Java 7中引入的,但仍不為人所知。
  • LambdaMetafactory –在Java 8中引入。它是動態(tài)調(diào)用概念的進(jìn)一步發(fā)展。 該API基于MethodHandle。

方法句柄API可以很好地替代標(biāo)準(zhǔn)反射,因?yàn)镴VM僅在MethodHandle創(chuàng)建期間執(zhí)行一次所有預(yù)調(diào)用檢查。 長話短說–方法句柄是對基礎(chǔ)方法,構(gòu)造函數(shù),字段或類似的低級操作的類型化,直接可執(zhí)行的引用,具有參數(shù)或返回值的可選轉(zhuǎn)換。

令人驚訝的是,除非您按照本電子郵件列表中的方法將MethodHandle引用設(shè)為靜態(tài),否則與反射API相比,純MethodHandle引用調(diào)用不會提供更好的性能。

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

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 ,因此不需要太多的重構(gòu)。 讓我們考慮一下事件偵聽器處理程序代碼–它是對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的方法參考進(jìn)行更改的方式:

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

該代碼具有微妙的更改,并且功能相同。 但是與傳統(tǒng)反射相比,它具有一些優(yōu)勢:

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

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

速度 –這是應(yīng)該衡量的事情。

標(biāo)桿管理

對于新版本的CUBA框架,我們創(chuàng)建了一個基于JMH的微基準(zhǔn),以比較“傳統(tǒng)”反射方法調(diào)用(基于lambda)的執(zhí)行時間和吞吐量,并添加了直接方法調(diào)用以進(jìn)行比較。 方法引用和lambda都是在測試執(zhí)行之前創(chuàng)建和緩存的。

我們使用了以下基準(zhǔn)測試參數(shù):

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

您可以從GitHub下載基準(zhǔn)測試,然后自己運(yùn)行測試。

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

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

如您所見,基于lambda的方法處理程序平均快30%。 關(guān)于基于lambda的方法調(diào)用性能, 這里有很好的討論。 結(jié)果-LambdaMetafactory生成的類可以被內(nèi)聯(lián),從而獲得一些性能改進(jìn)。 它比反射更快,因?yàn)榉瓷湔{(diào)用必須在每次調(diào)用時通過安全檢查。

該基準(zhǔn)測試是很貧乏的,沒有考慮類的層次結(jié)構(gòu),最終方法等,它只測量“公正”的方法調(diào)用,但足以滿足我們的目的。

實(shí)作

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

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

LambdaGetTest

在新版本中的外觀如下:

LambdaGetTest

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

結(jié)論

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

而且,如果您考慮在代碼中使用反射API,無論是實(shí)現(xiàn)自己的框架還是僅是應(yīng)用程序,請考慮另外兩個選項(xiàng)-代碼生成,尤其是LambdaMetafactory。 后者將提高代碼執(zhí)行速度,而與“傳統(tǒng)”反射API相比,開發(fā)不會花費(fèi)更多時間。

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

三思筆記

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。