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

三思笔记

介绍

有时,作为开发人员,您可能会遇到无法使用new运算符实例化对象的情况,因为其类名称存储在配置XML中的某个位置,或者您需要调用一个名称指定为注释属性的方法。 在这种情况下,您总会有一个答案:“使用反射!”。

在新版本的CUBA框架中 ,我们决定改进体系结构的许多方面,最重要的变化之一是在控制器UI中弃用了“经典”事件侦听器。 在该框架的先前版本中,屏幕的init()方法中注册了许多样板代码的侦听器,使您的代码几乎不可读,因此新概念应该可以解决此问题。

您始终可以通过为带注释的方法存储java.lang.reflect.Method实例来实现方法侦听器,并像在许多框架中实现的那样调用它们,但是我们决定看看其他选项。 反射调用需要付出一定的成本,如果您开发了生产级框架,则即使是很小的改进也可能在短时间内得到回报。

在本文中,我们将介绍反射API的用法,优缺点,并查看其他替代反射API调用的选项-AOT和代码生成以及LambdaMetafactory。

反射–良好的旧可靠API

根据维基百科,“反射是计算机程序在运行时检查,自省和修改其自身的结构和行为的能力”。

对于大多数Java开发人员而言,反射并不是新事物,它在许多情况下都被使用。 我敢说Java在没有反思的情况下不会变成现在的样子。 只需考虑批注处理,数据序列化,通过批注或配置文件进行方法绑定…对于最流行的IoC框架,由于广泛使用类代理,方法引用等,反射API是基石。此外,您还可以添加面向方面的编程到此列表–一些AOP框架依靠反射来进行方法执行拦截。

反射有什么问题吗? 我们可以考虑其中的三个:

速度 –反射呼叫比直接呼叫慢。 我们可以看到,随着每个JVM版本的发布,反射API的性能都有了很大的提高,JIT编译器的优化算法越来越好,但是反射方法的调用速度仍然比直接调用慢3倍。

类型安全性 –如果您在代码中使用方法引用,则它只是方法引用。 如果编写的代码通过其引用调用方法并传递错误的参数,则该调用将在运行时失败,而不是在编译时或加载时失败。

可追溯性 –如果反射方法调用失败,则可能很难找到导致这一问题的代码行,因为堆栈跟踪通常很庞大。 您需要深入研究所有这些invoke()proxy()调用。

但是,如果您研究Spring中的事件侦听器实现或Hibernate中的JPA回调,则会在其中看到熟悉的java.lang.reflect.Method引用。 而且我怀疑它是否会在不久的将来更改–成熟的框架又大又复杂,用在许多关键任务系统中,因此开发人员应该谨慎地进行重大更改。

让我们看看其他选项。

AOT编译和代码生成–使应用程序再次快速

反射替换的第一个候选人–代码生成。 如今,我们可以看到诸如Micronaut和Quarkus之类的新框架的兴起,它们针对两个目标:快速启动时间和低内存占用。 在微服务和无服务器应用程序时代,这两个指标至关重要。 最近的框架正试图通过使用提前编译和代码生成来完全摆脱反思。 通过使用注释处理,类型访问者和其他技术,他们将直接方法调用,对象实例化等添加到代码中,从而使应用程序更快。 那些不使用Class.newInstance()在启动期间创建和注入bean,不在侦听器中使用反射方法调用,等等。这看起来很有希望,但是这里有什么取舍吗? 答案是–是的。

第一个–您运行的代码不完全是您自己的。 代码生成会更改您的原始代码,因此,如果出现问题,您将无法确定是您的错误还是代码处理算法中的故障。 并且不要忘记,现在您应该调试生成的代码,而不是代码。

第二个权衡–您必须使用供应商提供的单独工具/插件才能使用该框架。 您不能“只是”运行代码,而应以特殊方式对其进行预处理。 并且,如果您在生产中使用框架,则应将供应商的错误修正应用于框架代码库和代码处理工具。

代码生成早已为人所知,但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创建期间执行一次所有预调用检查。 长话短说–方法句柄是对基础方法,构造函数,字段或类似的低级操作的类型化,直接可执行的引用,具有参数或返回值的可选转换。

令人惊讶的是,除非您按照本电子邮件列表中的方法将MethodHandle引用设为静态,否则与反射API相比,纯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

三思笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/332144.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分页机制总结

【0】写在前面(分页机制) 0.0) source code from orange’s implemention of a os and text description from Zhaojiong’s perfect analysis of Linux kernel and for complete code ,please visit https://github.com/pacosonTang/Orange…

python程序填空题参照代码模板、完善代码_python二级考试操作题11.pdf

综合应用题参照代码模板完善代码,实现下述功能。文件ngchina.html 保持了网页源代码,请将该页面中图片的URL 提取出来,并输出所有图像的URL。习题讲解#P301#读取HTML 文件内容def getHTMLlines(htmlpath):f open(htmlpath,"r",encoding utf-…

Struts2学习笔记

文章目录Struts2 的核心开发包Struts2 配置文件Struts2 域对象Struts2 编程流程Action 组件使用通配符配置 ActionAction 中如何访问 Session通过 ActionContext 对象访问 Session 对象(不推荐)通过实现 SessionAware 接口访问 sessionAction 如何访问 r…

如何从文件系统中读取文件内容

【0】写在前面 0.0) text description from orange’s implemention of a os ,文末总结系个人臆测出的干货 【1】intro to FAT12(file allocation table 12)文件系统格式(from Baidu Baike) (…

java微服务,微在哪_Java:ChronicleMap第3部分,快速微服务

java微服务,微在哪标准Java Maps需要在启动时进行初始化。 了解如何利用可从文件初始化的ChronicleMaps并显着减少微服务启动时间,以及如何在JVM之间共享Maps。 内置的Map实现(例如HashMap和ConcurrentHashMap速度很快,但是必须先使用映射进…

excel离散度图表怎么算_Excel数据分析——离散值排除-excel直方图

今天举例的数据继续沿用昨天做出来的结果,至于这组数据还要接着用多久~~可能要混到我讲不下去为止吧~~~我们通过两个不同的拟合公式得到了两组不同的残差值,数据情况如下:有没有觉得看上面那张散点图有点糊啊?没错,问题…

drools dmn_Drools DMN最新开源引擎性能改进

drools dmn我们一直在寻求改善Drools DMN开源引擎的性能。 我们最近审查了DMN用例,其中输入数据节点的实际输入总体有所不同。 这突出显示了引擎的次佳性能,我们在最新版本中对此进行了改进。 我想分享我们的发现! 基准制定 当我们开始为此用…

制作FAT12软盘以查看软盘的根目录条目+文件属性+文件内容

【-1】Before for specific info , please visit http://wiki.osdev.org/Loopback_Device 【0】我们先上干货,看到效果后,我们再说明每个步骤的缘由; 【1】进入挂载目录,添加相关文件(依个人意愿) Attenti…

如何取消高亮显示重复项_如何将重复数据突出显示?

将表格中一列数据中重复的,使用特殊颜色突出显示或者使用一些符号标记出来。例如:一个供应商,可以邀请别的供应商加入成为联合体,报表要显示所有供应商,然后供应商最后一列显示所有联合体,当联合体供应商跟…

Maven的maven-source-plugin插件详解

maven-source-plugin 这个插件专门负责将项目源文件打成包的&#xff0c;该插件在 pom.xml 中的配置如下&#xff1a; <build><plugins><plugin><artifactId>maven-source-plugin</artifactId><version>3.0.1</version><configu…

Maven Java Web Project打包详解/如何打包

文章目录打包部署构件&#xff08;Artifacts&#xff09;打源码包方式一&#xff1a; 命令行方式方式二&#xff1a;使用 IDE将源码包发布到本地 Maven 仓库中涉及到案例项目的结构&#xff1a; 打包部署构件&#xff08;Artifacts&#xff09; war 格式的部署构件可以直接放…

四位共阳极数码管显示函数_数码管模块.doc

数码管模块数码管1、概 述数码管模块采用四位共阳极数码管&#xff0c;用于显示数字和少数特殊字符。可以在机器人项目中使用该模块&#xff0c;用于显示速度、时间、分数、温度、距离等传感器的值。同时&#xff0c;Makeblock提供易于编程的Arduino库&#xff0c;使用户能够方…

java中的可检查和不检查_检查Java测试中发生了什么

java中的可检查和不检查有人想到了在Java单元测试中使用try和catch块的想法&#xff1a; Test public void test() { try { callSomeCode(); } catch (Exception e) { assertEquals( "foo" , e.getMessage()); } } 上面的内容很诱人&#xff0c;但不起作用 。 如果…

FAT12中,如何定位大于一个扇区(512B)的文件内容

【0】README 0.1&#xff09;本文旨在于 演示在FAT12中&#xff0c; 如何取定位大于 512B 的文件内容&#xff0c;和查看它&#xff1b;0.2&#xff09;如何制作FAT12文件系统&#xff0c;以及如何向文件中添加temp.txt文件&#xff0c;参见&#xff1a; { http://blog.csdn.n…

通过Struts2的拦截器实现文件上传/上传文件功能

struts.xml配置内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN""http://struts.apache.org/dtds/struts-2.3.dtd&qu…

java获取ram_Java:ChronicleMap第2部分,超级RAM映射

java获取ram诸如无处不在的HashMap类的标准Java Map最终受到可用RAM的限制。 阅读本文并了解如何创建几乎无限大小甚至超过目标计算机RAM大小的Java Map。 内置的Map实现&#xff0c;例如HashMap和ConcurrentHashMap &#xff0c;只要它们相对较小&#xff0c;就可以正常工作。…

lifi与wifi的论文_Wifi_(毕业论文).doc

Wifi_(毕业论文)绪论2第一章 Wi-Fi技术概述31.1 无线局域网络31.1.1 IEEE 802.11系列标准31.1.2 无线局域网络概述31.2 Wi-Fi概念的引入41.2.1 Wi-Fi技术41.2.2 怎样使用Wi-Fi41.2.3 谁可以使用Wi-Fi5第二章 Wi-Fi技术的分析62.1 Wi-Fi与其他技术的对比分析6参 考 文 献8绪论Wi…

os引导程序boot从扇区拷贝os加载程序loader文件到内存(boot copy kernel to mem in the same method)

【0】README 0.1&#xff09; 本代码旨在演示 在boot 代码中&#xff0c;如何 通过 loader文件所在根目录条目 找出该文件的 在 软盘所有全局扇区号&#xff08;簇号&#xff09;&#xff0c;并执行内存中的 loader 代码&#xff1b;0.2&#xff09; 此代码非常重要&#xff0…

Struts2的资源文件和国际化(i18n)

文章目录资源文件的命名资源文件基名的指定struts.xml 文件指定资源文件的基名struts.properties 文件指定资源文件的基名通过标签 i18n 指定资源文件的基名资源文件的位置包级资源文件类级资源文件全局级资源文件默认资源文件资源文件的加载顺序Action 中加载资源文件在 JSP 中…

java设计模式之装饰模式_Java中的装饰器设计模式

java设计模式之装饰模式装饰器设计模式允许在运行时将附加职责或行为动态附加到对象。 它是一种结构模式&#xff0c;利用聚合来组合这些行为。 在本教程中&#xff0c;我们将学习实现装饰器模式。 UML图&#xff1a; 让我们从装饰器模式的UML表示开始&#xff1a; Concrete…