excel批注不显示批注框
在Java中,大多数情况下,批注和批注处理器都被一团谜团包围。 他们看起来像是为“专家”保留的主题。 最重要的是,我相信他们周围也有一些FUD。
这篇文章旨在以最中立的方式深入探讨该主题。 这样,每个人都可以根据事实做出明智的决定,而不必听取充满误解或隐藏议程的人们的意见。
自Java版本5(代号为Tiger)于发布以来,便可以使用注释。
在Java计算机编程语言中,注释是一种语法元数据,可以添加到Java源代码中。 类,方法,变量,参数和Java包可能会带有注释。—维基百科
/wiki/Java_annotation
最简单的注释如下所示:
@MyAnnotation public class Foo {}
由于缺少注释,以前的Java版本必须以倾斜的方式使用某些功能。
更换标记器接口
自Java诞生以来,就需要标记一个类或层次结构
类。 在Java 5之前,这是通过没有方法的接口完成的。Serializable
和可Cloneable
是此类接口的两个示例。
这种接口显然不同于其他接口:它们在自己和实现类之间未定义任何协定。 因此,他们赢得了标记器接口的名称。
Java新手通常会问与该方法有关的问题。 这样做的原因是因为这是一个把戏。 注释消除了对该技巧的需要,并保留了接口的协定角色。
public class Foo implements MarkerInterface {} // 1@MyAnnotationpublic class Foo {} // 2
标记界面 等同于标记界面的注释
更好的元数据管理
弃用是将API标记为过时的过程。 这样,用户可以获知有关更改的信息,可以决定停止使用该API,并且可以在以后的版本中以较小的影响删除该API。 在Java 5之前,JavaDoc中设置了弃用:
/*** Blah blah JavaDoc.** @deprecated As of JDK version 1.1,*/public class DeprecatedApi {}
显然,这是一种非常脆弱的方法:唯一利用它的方法是通过javadoc
工具。 标准JavaDocs 专门讨论了这些不推荐使用的API 。 或者,可以通过自定义doclet配置javadoc
工具,以以任何所需方式处理Javadoc元数据(包括但不限于@deprecated
)。
Java 5中,弃用使用提供的@Deprecated
批注进行标记:
/*** Blah blah JavaDoc.*/@Deprecatedpublic class DeprecatedApi {}
注意:旧的不推荐使用的API保留了旧的方法,因此它们同时使用元数据和注释。
此外,由于Java 9,@Deprecated
允许两个元素:
forRemoval
(类型为boolean
):指示带注释的元素是否在将来的版本中会被删除since
(类型为String):返回不支持带注释的元素的版本
@Deprecated (since= "1.2" , forRemoval= true )public abstract class IdentityScope extends Identity {}
创建注释
要创建注释,请使用@interface
关键字:
public @interface MyAnnotation {}
但是,这还不够,因为不能在任何地方设置这样的注释。 注释需要另外两条信息:
目标:定义可以在何处设置注释保留:这说明了注释在编译过程中的哪个步骤可用
稍后我们将详细介绍。 至于现在,我们首先需要
了解注释的工作方式。 虽然类从其继承代码
父类,注释组成。
@Target (ElementType.ANNOTATION_TYPE) // 1@interface Foo {}@Target (ElementType.ANNOTATION_TYPE) // 1 @interface Bar {}@Foo@Bar@interface Baz {} // 2
这是必需的@Target
,将在@Target
进一步解释 用@Baz
注释的内容同时用@Foo
和@Bar
注释
这是@Target
和@Retention
的源代码:
@Retention (RetentionPolicy.RUNTIME) // 2@Target (ElementType.ANNOTATION_TYPE) // 1public @interface Target { ElementType[] value();} @Retention (RetentionPolicy.RUNTIME) // 2@Target (ElementType.ANNOTATION_TYPE) // 1public @interface Retention { RetentionPolicy value () ;}
@Target
注释告诉您可以在哪个元素上设置注释: 在类型上,例如类或接口 在另一个注释上 在田野上 在方法上 在构造函数上 在方法参数上在局部变量上在模块上等@Retention
注释定义了注释在编译过程中的哪个步骤可用: 仅源代码 在类文件中 在运行时
下面的类图中对此进行了总结:
注释参数
注释可以定义参数。 使用注释时,可以使用参数添加某些级别的配置。 参数接受类型和可选的默认值。 如果在定义注释时未设置该值,则必须在使用它时使用。
参数类型限于以下几种:
任何原始类型,例如int
,long
等。String
Class<T>
任何enum
类型 另一种注释类型 以上任何数组
@Target (ElementType.CLASS)@interface Foo {int bar () ;Class<? extends Collection> baz() default List.class;String[] qux();} @Foo (bar = 1 , qux = { "a" , "b" , "c" })class MyClass {}
如果只有一个参数,并且它是命名值,则在设置时可以省略其名称:
@Target (ElementType.CLASS)@interface Foo {int value () ;} @Foo ( 1 )class MyClass {}
在运行时处理批注:反射
自创建以来,Java就允许反射:反射是在运行时获取有关代码信息的能力。 这是一个示例:
var session = request.getHttpSession();var object = session.getAttribute( "objet" ); // 1 var clazz = object.getClass();// 2 var methods = clazz.getMethods(); // 3 for ( var method : methods) {if (method.getParameterCount() == 0 ) { // 4 method.invoke(foo); // 5 }}
获取存储在会话中的对象 获取对象的运行时类 获取对象上所有可用的public
方法 如果该方法没有参数 调用方法
通过注释,反射API得到了相关的改进:
用
注释,框架开始将它们用于不同的
用例。 其中,配置是最常用的配置之一:
例如, Spring框架代替了XML(或者更精确地说是XML之外),添加了基于注释的配置选项。
在编译时处理批注:批注处理器
长期以来,用户和提供者都对运行时反射对批注的访问感到满意。 因为它主要集中在配置上,所以反射在启动时发生。 在受限的环境中,这对应用程序来说负担太重:此类环境最著名的示例是Android平台。 人们可能希望在那里拥有最快的启动时间,而启动时间反射方法会使速度变慢。
解决该问题的另一种方法是在编译时处理注释。 为此,必须将编译器配置为使用特定的注释处理器。 它们的输出可能不同:简单的文件,生成的代码等。这种方法的权衡之处在于,每次编译都会对性能造成影响,但不会影响启动时间。
使用此方法生成代码的最早框架之一是Dagger :它是Android的Dependency-Injection框架。 它不是基于运行时的,而是基于编译时的。 长期以来,编译时代码生成仅限于Android生态系统。
但是,最近,诸如Quarkus和Micronaut的后端框架
也采用了这种方法。 目的是通过编译时代码生成来代替运行时自省,以减少应用程序启动时间。 此外,将生成的字节码提前编译为本机代码还可以进一步减少启动时间以及内存消耗。
注释处理器的世界是巨大的:本节是一个但非常
简短的介绍,因此如果需要可以继续进行。
处理器只是需要在编译时注册的特定类。 有几种注册方法。 使用Maven,只需配置编译器插件即可:
< build >< plugins >< plugin >< groupId > org.apache.maven.plugins </ groupId >< artifactId > maven-compiler-plugin </ artifactId >< version > 3.8.1 </ version >< configuration >< annotationProcessors >< annotationProcessor >ch.frankel.blog.SampleProcessor</ annotationProcessor ></ annotationProcessors ></ configuration ></ plugin ></ plugins ></ build >
处理器本身需要实现Processor
,但是抽象类AbstractProcessor
实现其大多数方法,但可以进行处理:实际上,从AbstractProcessor
继承就足够了。 这是API的简化图:
让我们创建一个非常简单的处理器。 它应该只列出那些
带有特定注释。 真实世界注解
处理器可能会做一些有用的事情,例如生成代码,但是这种额外的逻辑远远超出了本文的范围。
@SupportedAnnotationTypes ( "ch.frankel.blog.*" ) // 1@SupportedSourceVersion (SourceVersion.RELEASE_8)public class SampleProcessor extends AbstractProcessor { @Overridepublic boolean process (Set<? extends TypeElement> annotations,// 2 RoundEnvironment env) {annotations.forEach(annotation -> {// 3 Set<? extends Element> elements =env.getElementsAnnotatedWith(annotation); // 4elements.stream().filter(TypeElement.class::isInstance) // 5.map(TypeElement.class::cast) // 6 .map(TypeElement::getQualifiedName) // 7 .map(name -> "Class " + name + " is annotated with " + annotation.getQualifiedName()).forEach(System.out::println);});return true ;}}
属于ch.frankel.blog
包的每个注释都将调用Processor
process()
是重写的主要方法 将为每个注释调用循环 注释不像使用注释的元素那么有趣。 这是获取带注释元素的方法。 根据要注释的元素,需要将其强制转换为正确的Element
子接口。 在这里,只能注释类,因此,需要对变量进行测试,以检查其是否可分配的TypeElement
才能在操作链的更下方访问其附加属性。 我们想要设置注释的类的限定名称,所以它是
必须将其强制转换为构成此特定属性的类型
无障碍 从TypeElement
获取合格名称
结论
无论在运行时还是在编译时使用,注释都非常强大。 另一方面,最大的问题是它们似乎像魔术一样工作:没有简单的方法来知道哪个使用反射的类或注释处理器正在使用它们。 每个人都可以根据自己的情况决定自己的优点是否超过缺点。 在没有任何深思熟虑的情况下使用它们会对代码造成极大的损害...
由于放错位置而造成的损失与丢弃它们一样严重
思想。
我希望这篇文章对注释的工作方式有所启发,以便大家自己决定。
可以在Github上以Maven格式找到此帖子的完整源代码。
更进一步:
Java中的标记接口 Doclet概述 Java语言规范:注释类型 Java语言规范:注释 Trail:反射API Java注释处理和创建生成器
首次发布于4月26日于A Java Geek
翻译自: /a-beginners-guide-to-annotations-and-annotation-processors-explained-wu6r321s
excel批注不显示批注框