注释处理和JPMS

TLDR; 代替annotation.getClass().getMethod("value")调用annotation.annotationType().getMethod("value")

所有Java开发人员都听说过注释。 自Java 1.5(或者您坚持认为只有1.6)以来,我们便有了注释。 根据我与应聘者面试的经验,我觉得大多数Java开发人员都知道如何使用注释。 我的意思是,大多数开发人员都知道它看起来像@Test@Override ,并且它们是Java或某些库附带的,必须在类,方法或变量的前面编写。

一些开发人员知道,您还可以使用@interface在代码中定义注释,并且您的代码可以使用注释进行一些元编程。 很少有人知道注释可以由注释处理器处理,并且其中一些可以在运行时进行处理。

我可以继续,但长话短说,对于大多数Java开发人员来说,注释是一个谜。 如果您认为我错了,说明大多数Java开发人员与注释之间的联系毫无头绪,那么请考虑一下,在过去30年中,程序员(通常是编码人员)的数量呈指数级增长,而Java开发人员(尤其是在这样做)因此在过去的20年中,它仍在呈指数增长。 指数函数具有此功能:如果whatnot的数量呈指数增长,则大多数whatnot都是年轻的。
这就是为什么大多数Java开发人员不熟悉注释的原因。

老实说,注释处理并不是一件简单的事情。 它值得拥有自己的文章,特别是当我们想在使用模块系统时处理注释时。

在Java :: Geci代码生成框架的1.2.0版的最后修订中,我遇到了一个问题,该问题是由于我对注释和反射的错误使用而引起的。 然后我意识到,可能大多数使用反射处理批注的开发人员都以相同的错误方式这样做。 网上几乎没有任何线索可以帮助我理解问题。 我发现的只是一张GitHub票 ,根据那里的信息,我不得不弄清楚到底发生了什么。

因此,让我们刷新一下注释是什么,然后让我们看一下到目前为止可能做错了什么,但是当JPMS出现在图片中时可能会引起麻烦。

什么是注释?

注释是使用@字符开头的interface关键字声明的interface 。 这使得注释可以按照我们习惯的方式在代码中使用。 使用注释接口的名称,并在其前面加上@ (例如:@Example)。 最常用的此类注释是Java编译器在编译期间使用的@Override

许多框架在运行时使用注释,其他框架则进入实现注释处理器的编译阶段。 我写了有关注释处理器以及如何创建注释处理器的文章。 这次,我们将重点放在更简单的方法上:在运行时处理注释。 我们甚至没有实现注释接口,这是一种很少使用的可能性,但是如本文所述 ,它很复杂且难以执行。

要在运行时使用注释,注释必须在运行时可用。 默认情况下,注释仅在编译时可用,并且不会进入生成的字节码中。 忘记(我总是这样做)是一个常见的错误,我将@Retention(RetentionPolicy.RUNTIME)批注放在批注界面上,然后开始调试为什么当我使用反射访问批注时为什么看不到批注。

一个简单的运行时批注如下所示:

 @Retention (RetentionPolicy.RUNTIME)  @Repeatable (Demos. class )  public @interface Demo { String value() default "" ;  } 

当在类,方法或其他带注释的元素上使用时,注释具有参数。 这些参数是界面中的方法。 在该示例中,接口中仅声明了一种方法。 它称为value() 。 这是一个特殊的。 这是一种默认方法。 如果没有注释接口的其他参数,或者即使没有,但我们不想使用其他参数并且它们都具有默认值,则可以编写

 @Demo ( "This is the value" ) 

代替

 @Demo (value= "This is the value" ) 

如果需要使用其他参数,则没有此快捷方式。

如您所见,注释是在某些现有结构之上引入的。 接口和类用于表示注释,这并不是Java中引入的全新内容。

从Java 1.8开始,在带注释的元素上可以有多个相同类型的注释。 您甚至可以在Java 1.8之前拥有该功能。 您可以定义另一个注释,例如

 @Retention (RetentionPolicy.RUNTIME)  public @interface Demos { Demo[] value();  } 

然后在带注释的元素上使用此包装器注释,例如

 @Demos (value = { @Demo ( "This is a demo class" ), @Demo ( "This is the second annotation" )})  public class DemoClassNonAbbreviated {  } 

为了缓解因过度输入而引起的肌腱炎,Java 1.8引入了注记Repeatable (如在注解接口Demo上所见),因此上述代码可以简单地编写为

 @Demo ( "This is a demo class" )  @Demo ( "This is the second annotation" )  public class DemoClassAbbreviated {  } 

如何使用反射读取注释

现在我们知道注释只是一个接口,接下来的问题是我们如何获取有关它们的信息。 传递有关注释信息的方法在JDK的反射部分中。 如果我们有一个可以带有注释的元素(例如, ClassMethodField对象),则可以在该元素上调用getDeclaredAnnotations()以获取该元素具有的所有注释或getDeclaredAnnotation() ,以防万一我们知道要使用什么注释需要。

返回值是注释对象(在第一种情况下为注释数组)。 显然,它是一个对象,因为所有内容都是Java中的对象(或原始类型,但注解不是原始类型)。 该对象是实现注释接口的类的实例。 如果我们想知道程序员在括号之间写了什么字符串,我们应该写类似

 final var klass = DemoClass. class ;  final var annotation = klass.getDeclaredAnnotation(Demo. class );  final var valueMethod = annotation.getClass().getMethod( "value" );  final var value = valueMethod.invoke(annotation);  Assertions.assertEquals( "This is a demo class" , value); 

因为value是接口中的一种方法,可以肯定地由我们可以通过其实例之一访问的类实现,所以我们可以反射性地调用它并返回结果,在这种情况下为"This is a demo class"

这种方法有什么问题

只要我们不在JPMS领域,通常什么都没有。 我们可以访问该类的方法并调用它。 我们可以访问接口的方法并在对象上调用它,但实际上,它是相同的。 (或者对于JPMS则不是。)

我在Java :: Geci中使用了这种方法。 该框架使用@Geci批注来标识哪些类需要将生成的代码插入其中。 它具有相当复杂的算法来查找批注,因为它可以接受任何名称为Geci批注,无论其位于哪个包中,并且还可以接受带有Geci批注的任何@interface (其名称为Geci或批注具有递归Geci的注释)。

这种复杂的注释处理有其原因。 该框架很复杂,因此使用起来很简单。 您可以说:

 @Geci ( "fluent definedBy='javax0.geci.buildfluent.TestBuildFluentForSourceBuilder::sourceBuilderGrammar'" ) 

或者您可以拥有自己的注释,然后说

 @Fluent (definedBy= "javax0.geci.buildfluent.TestBuildFluentForSourceBuilder::sourceBuilderGrammar" ) 

该代码在Java 11之前一直运行良好。当使用Java 11执行该代码时,我从其中一项测试中得到以下错误

 java.lang.reflect.InaccessibleObjectException:  Unable to make public final java.lang.String com.sun.proxy.jdk.proxy1.$Proxy12.value()  accessible: module jdk.proxy1 does not  "exports com.sun.proxy.jdk.proxy1" to module geci.tools 

(为了方便阅读,插入了一些换行符。)

JPMS的保护开始发挥作用,它不允许我们访问不应有的JDK中的某些内容。 问题是我们真正在做什么,为什么要做?

在JPMS中进行测试时,我们必须在测试中添加很多--add-opens命令行参数,因为测试框架希望使用库用户无法访问的反射来访问部分代码。 但是,此错误代码与Java :: Geci内部定义的模块无关。

JPMS保护库免遭滥用。 您可以指定哪些包包含可从外部使用的类。 即使其他软件包包含公共接口和类,也只能在模块内部使用。 这有助于模块开发。 用户无法使用内部类,因此只要保留API,您就可以自由地重新设计它们。 文件module-info.java这些软件包声明为

 module javax0.jpms.annotation.demo.use { exports javax0.demo.jpms.annotation;  } 

导出包时,可以直接或通过反射访问包中的类和接口。 还有另一种方式可以访问包中的类和接口。 这是打开包装。 为此的关键字是opens 。 如果module-info.javaopens包,则只能通过反射访问。

上面的错误消息说模块jdk.proxy1在其module-info.java中不包含exports com.sun.proxy.jdk.proxy1的行。 您可以尝试添加add-exports jdk.proxy1/com.sun.proxy.jdk.proxy1=ALL_UNNAMED但是它不起作用。 我不知道为什么它不起作用,但它不起作用。 实际上,它不起作用是一件好事,因为com.sun.proxy.jdk.proxy1包是JDK的内部部分,就像unsafe的过去一样,在过去使Java头疼不已。

与其尝试非法打开藏宝箱,不如让我们关注为什么首先要打开藏宝箱,以及我们是否真的需要打开藏宝箱?

我们要做的是访问类的方法并调用它。 我们不能这样做,因为JPMS禁止这样做。 为什么? 因为Annotation对象类不是Demo.class (这很明显,因为它只是一个接口)。 相反,它是实现Demo接口的代理类。 该代理类是JDK的内部对象,因此我们不能调用annotation.getClass() 。 但是,当我们要调用批注的方法时,为什么还要访问代理对象的类呢?

长话短说(我的意思是要花几个小时进行调试,研究和理解,而不是没人管闲事的堆栈溢出复制/粘贴):我们一定不能碰触实现注释接口的类的value()方法。 我们必须使用以下代码:

 final var klass = DemoClass. class ;  final var annotation = klass.getDeclaredAnnotation(Demo. class );  final var valueMethod = annotation.annotationType().getMethod( "value" );  final var value = valueMethod.invoke(annotation);  Assertions.assertEquals( "This is a demo class" , value); 

或者

 final var klass = DemoClass. class ;  final var annotation = klass.getDeclaredAnnotation(Demo. class );  final var valueMethod = Demo. class .getMethod( "value" );  final var value = valueMethod.invoke(annotation);  Assertions.assertEquals( "This is a demo class" , value); 

(这已在Java :: Geci 1.2.0中修复。)我们具有注释对象,但是除了要求它的类外,我们还必须访问annotationType() ,后者是我们编写的接口本身。 那是模块导出的东西,因此我们可以调用它。

我的儿子MihályVerhás(也是EPAM的Java开发人员)通常会审阅我的文章。 在这种情况下,“审查”被扩展了,他在文章中写了一个不可忽略的部分。

翻译自: https://www.javacodegeeks.com/2019/08/annotation-handling-jpms.html

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

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

相关文章

Cassandra中的数据建模

在关系数据模型中,我们为域中的每个对象建模关系/表。 对于Cassandra,情况并非如此。本文将详细介绍在Cassandra中进行数据建模时需要考虑的所有方面。 以下是Cassandra数据建模的粗略概述。 从上图可以看出, 概念数据建模和应用程序查询是构…

485串口光纤转换器产品介绍

光纤转换器是RS-232/422/485串行数据通过光纤的远距离传输,可以完成串口到光纤的转换,并且可以延长串行通信信号的传输距离。接下来我们就跟随飞畅科技的小编一起来详细了解下光纤转换器的转换类别有哪些?一起来看看吧! 光纤转换器…

什么是485转光纤?485光纤转换器功能特点及技术参数详解

RS485转光纤转换器实现一路RS-485在光纤上的透明传输,由于采用光纤通信,解决了电磁干扰、地环干扰和雷电破坏的难题,大大提高了数据通讯的可靠性、安全性和保密性,可广泛用于各种工业控制、过程控制、交通控制和分布式数据采集等场…

什么是协议转换器?协议转换器的定义

现如今,随着各种类型的转换器的出现,在很大程度上扩大了各类仪表的使用范围,使的自动控制系统具有更多的灵活性和更广的适应性,在这方面,协议转换器的应用范围也是非常的广泛。今天,杭州飞畅的小编就来为大…

什么是网络协议转换器?

通过之前的介绍,我们了解到协议转换是一种映射,就是把某一协议的收发信息(或事件)序列映射为另一协议的收发信息序列。那么,什么是网络协议转换器呢?接下来就跟随飞畅科技的小编一起来看看吧! 网络协议转换器是什么&a…

midlet_如何在J2ME中创建MIDlet

midlet总览 Java移动应用程序称为J2ME。 通常,当我们在移动技术领域工作时,我们必须考虑J2ME应用程序。 通过这种方式,我们可以开发我们的移动应用程序,也可以通过jad或jar文件将其安装在我们的设备中。 近年来,手机开…

网关到底是什么?协议转换器是网关吗?

网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。那么,网关到底是什么呢?接下来…

穿越JUnit流

关于JUnit 5迁移的好处之一是,您可以在老式模式下运行JUnit 4测试,并且所有内容仍然兼容。 不利的一面是,某些注释和方法在JUnit 4和JUnit 5中具有相同的名称,并且当两组库依赖项都可用时,很容易导入错误的内容并产生不…

协议转换器安全使用须知

协议转换器能使处于通信网上采用不同高层协议的主机仍然互相合作,完成各种分布式应用。可以将IEEE802.3协议的以太网或V.35数据接口同标准G.703协议的2M接口之间进行相互转换。也可以在232/485/422串口和E1、CAN接口及2M接口进行转换。那么,我们在使用协…

怎么安装协议转换器?协议转换器安装方法解析

协议转换器是有很多种的,多数基本上是个2层设备,经常碰见的一种RAD的协议转换器是将2M的E1线路转换成V.35的数据线路连接路由器的设备,当然也有,2M转双绞线以太的,借助2M通信线路可以实现局域网范围的远程接入和扩大。…

关于协议转换器的分类以及工作原理的详细介绍

现如今,随着互联网的广泛应用,我们国内的网民也是突破了8.29亿,相信,大家对于网络这块是非常的熟悉了,它是一种虚拟的东西,但是它几乎存在于我们生活的各个角落,在很大程度的让我们的日常生活变…

ElasticSearch-Hadoop:从Hadoop到ElasticSearch索引产品视图计数和客户顶部搜索查询

这篇文章涵盖了如何使用ElasticSearch-Hadoop从Hadoop系统读取数据并在ElasticSearch中对其进行索引。 它涵盖的功能是在最近n天中为每个客户的产品浏览量计数和热门搜索查询编制索引。 可以在网站上进一步使用分析后的数据来显示最近浏览过的客户,产品浏览次数和热…

协议转换器是怎么分类的?主要有哪些类别?

工业通信采用的通信接口各不相同,需要多个设备之间的信息共享和数据交换,而常用的工控设备通信口有RS-232、RS-485、CAN和网络,由于各种通信结构的协议不兼容,使得异构网络之间的操作和信息交换难以进行,通过多协议转换…

协议转换器的特点有哪些?

现如今,随着计算机网络技术的迅猛发展,我们建立了大量的多种多样的网络系统,导致各种网络之间如何互连的问题。一个办法是推行国际标准,051网络体系结构及通信协议的国际标准已越来越成熟。但是,要把大量已存在的非051…

协议转换器的作用有哪些?

协议转换器一般用一个ASIC芯片就可以完成,成本低,体积小。它可以将IEEE802.3协议的以太网或V.35数据接口同标准G.703协议的2M接口之间进行相互转换。也可以在232/485/422串口和E1、CAN接口及2M接口进行转换,那么协议转换器的作用有哪些呢&…

协议转换器主要是应用在哪些地方?

协议转换器也叫接口转换器,它能使处于通信网上采用不同高层协议的主机仍然互相合作,完成各种分布式应用,它工作在传输层或更高。那么,协议转换器的作用有哪些呢?主要是应用在什么地方的呢?接下来就跟随飞畅…

协议转换器指示灯的含义

很多对协议转换器了解不是很深的朋友,对协议转换器上面的各个指示灯可能会区分不清,很多情况下会搞不清各个指示灯的含义,接下来杭州飞畅科技的小编就来带大家详细了解下协议转换器上各个指示灯的含义,一起来看看吧! …

V.35协议转换器指示灯告警常见故障以及排除方法解析

我们在使用协议转换器的时候,可能常常会遇到一些故障问题,这个可能会困扰到很多朋友,在这里,杭州飞畅的小编专门整理了V.35系列协议转换器在使用过程中的一些常见的故障问题及解决方法,感兴趣的朋友可以一起来看看吧&a…

飞畅科技V.35协议转换器指示灯告警说明详细介绍

飞畅V.35系列协议转换器实现了E1接口与V.35接口之间的相互转换,是杭州飞畅科技推出的又一款广泛使用、备受好评的设备。此转换器的E1接口支持成帧和透明两种模式,广泛应用于计算机网络互连、DDN数据网接入、移动电话网的传输优化和基于E1的PCM网络的各种…

计算机图形学论文_论图计算

计算机图形学论文自从机械计算开始以来,图形概念就已经存在,并且在纯数学领域已经存在了数十年。 由于数据库的黄金时代,图形在软件工程中变得越来越流行。 图形数据库提供了一种持久化和处理图形数据的方法。 但是,图形数据库并不…