使用反射前先三思

介绍

有时,作为开发人员,您可能会遇到无法使用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

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

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

相关文章

解决 ZLibrary 登录/注册不了的问题

一 文章转载链接内容 转载链接:解决 ZLibrary 登录/注册不了的问题 - 知乎 很多小伙伴反馈说 Z-Library 能打开,但是不能登录。这实际上是由于官方登录入口受限导致的。话虽如此,我们仍然可以通过某些方法绕过这个限制。 >虽然我们注册时…

阿里云资源搜索网站

1. 奈斯搜索 - 资源超丰富的阿里云盘资源搜索网站! 2. 求资源-百度网盘资源分享_云盘资源分享网_云盘资源网 3. 猫狸盘搜 - 阿里云盘搜索神器 4. 云盘资源分享_云盘网盘资源共享_云盘资源搜索_阿里云盘资源-盘友社区

gradle入门_Gradle入门:简介

gradle入门Gradle是一种构建工具,可以用基于Groovy编程语言的内部DSL替换基于XML的构建脚本。 最近它吸引了很多关注,这就是为什么我决定仔细研究一下。 这篇博客文章是我的Gradle教程的第一部分,它有两个目标: 帮助我们安装Gr…

解决Chrome窗口总保持在最前面的问题 get rid of Chrome window always-on-top behavior

今天发现Chrome的窗口总保持在最前面,本以为是自己不小心把“总在最前”的功能打开了呢。结果找了半天也没发现Chrome有这个设置,于是想到了可能是个bug。我上网搜了下,发现大家遇到了跟我一样的问题,而且有人也向google反馈了这个…

Spring MVC – HTTP消息转换器

通常,您需要为用户提供相同的数据,但格式不同,例如JSON,PDF,XLS等。如果您的应用程序是基于Spring Framework的,则可以使用HTTP消息转换器来完成此任务。 需要将HTTP请求(或其部分)…

C++ 链表的建立与使用

#include <iostream> using namespace std; struct ListNode {int val; //存储数据ListNode *next; //next指针ListNode(int x) : val(x), next(NULL) {} }; ListNode* creatLinkedList(int arr[],int n) {if(n 0)return NULL;ListNode* head new ListNode(arr[…

排队论游乐场的游乐项目_外汇游乐场

排队论游乐场的游乐项目介绍 F X Playground是基于JavaFX的原型制作工具或实时编辑器&#xff0c;它消除了编译Java代码的步骤。 这个概念并不新鲜&#xff0c;例如在网络世界中&#xff0c;有许多HTML5 游乐场提供在线编辑器&#xff0c;使开发人员可以快速原型化或尝试各种Ja…

Node.js安装及环境配置之Windows篇

原博文链接&#xff1a;Node.js安装及环境配置之Windows篇 - 刘奇云 - 博客园 from:https://www.cnblogs.com/zhouyu2017/p/6485265.html 一、安装环境 1、本机系统&#xff1a;Windows 10 Pro&#xff08;64位&#xff09; 2、Node.js&#xff1a;v6.9.2LTS&#xff08;64位…

Java代理初学者指南

尽管Java初学者很快学会了键入public static void main来运行他们的应用程序&#xff0c;但是即使是经验丰富的开发人员也常常不知道JVM对Java流程的两个附加入口点的支持&#xff1a; premain和agentmain方法。 这两种方法都允许所谓的Java代理在驻留在其自己的jar文件中的同时…

npm WARN logfile could not create logs-dir: Error: EPERM: operation not permitted, mkdir ‘地址

场景&#xff1a;在windows系统下&#xff0c;安装node之后&#xff0c;查看npm版本&#xff0c;报错如图所示&#xff1a; 原因&#xff1a;是node目录权限不够&#xff1b; 解决方法&#xff1a;找到node目录&#xff0c;右键属性 > 安全 > 设置users用户完全控制权限…

javafx 自定义控件_JavaFX技巧10:自定义复合控件

javafx 自定义控件用JavaFX编写自定义控件是一个简单直接的过程。 需要一个控件类来控制控件的状态&#xff08;因此命名&#xff09;。 外观需要控件的外观。 而且通常不是用于自定义外观CSS文件。 控件的一种常见方法是将其正在使用的节点隐藏在其外观类中。 例如&#xff0…

虚拟机与容器 的 区别

VM和容器都可以帮助您充分利用可用的计算机硬件和软件资源。容器是块中的新孩子&#xff0c;但VM已经并且将继续在各种规模的数据中心中非常受欢迎。 如果您正在寻找在云中运行自己的服务的最佳解决方案&#xff0c;您需要了解这些虚拟化技术&#xff0c;它们如何相互比较&…

thymeleaf片段使用_Thymeleaf –片段和angularjs路由器局部视图

thymeleaf片段使用百里香叶许多很酷的功能之一就是能够渲染模板片段–我发现这是与AngularJs一起使用的特别有用的功能。 可以将AngularJS $ routeProvider或AngularUI路由器配置为返回不同“路径”的部分视图&#xff0c;使用百里香叶返回这些部分视图确实效果很好。 考虑一…

经典 Linux 协议栈——网络子系统

目录&#xff1a; 1.Linux网络子系统的分层 2.TCP/IP分层模型 3.Linux 网络协议栈 4.Linux 网卡收包时的中断处理问题 5.Linux 网络启动的准备工作 6.Linux网络包&#xff1a;中断到网络层接收 7.总结 Linux网络子系统的分层 Linux网络子系统实现需要&#xff1a; l …

Java和JavaScript之间的区别

1.简介 我们将在本文中比较Java语言和JavaScript语言。 JavaScript由Netscape开发。 它最初是用于客户端的脚本语言&#xff0c;后来又用作客户端和服务器脚本的语言。 Java由James Gosling由Sun Microsystems开发。 这些天来&#xff0c;JavaScript在服务器中以node.js的形式使…

《汇编语言》王爽实验DOS 环境 Win10 配置

下载这两个软件。 软件链接百度网盘 请输入提取码 提取码: y1j4 1. 将debug.exe放入一个文件夹中&#xff0c;用英文名&#xff0c;不要用中文。 我这里放在E盘下的Debug文件夹。 2 然后安装DOSBox软件。 安装好后在其文件目录下找到DOSBox 0.74-3 Options.bat 打开这个文件&…

硒4 Alpha –期望什么?

硒4 Alpha-期望什么&#xff1f; 早在2018年8月&#xff0c;整个测试自动化社区就受到了一个重大新闻的打击&#xff1a;Selenium的创始成员Simon Stewart在班加罗尔Selenium会议上正式确认了Selenium 4的发布日期和一些重大更新。 世界最受欢迎的Web测试自动化框架的4.0版本计…

8. 字符串转换整数 (atoi) [2022.10.21]

题目链接&#xff1a; 8. 字符串转换整数 (atoi) 一 题意介绍 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串…

26. 删除有序数组中的重复项[2022.10.24]

题目链接力扣 难度&#xff1a;简单 一 题目大意 26. 删除有序数组中的重复项给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某…

hibernate jpa_JPA / Hibernate实体状态转换的初学者指南

hibernate jpa介绍 Hibernate将开发人员的思维方式从SQL语句转移到实体状态转换。 一旦由Hibernate主动管理实体&#xff0c;所有更改将自动传播到数据库。 操作域模型实体&#xff08;及其关联&#xff09;比编写和维护SQL语句容易得多。 如果没有ORM工具&#xff0c;则添加新…