高级工程师-Java注解
高级工程师-java注解
前言
代码,就是我们身为程序员的名片。
简洁,优雅,统一,是我们的追求。
优秀的代码,会给浏览者一种艺术的美感。如dl大神的juc包,感兴趣的小伙伴,可以研究一下。
那么日常中,各位看到的优秀代码,有着哪些特点呢?充分利用的工具类(lang3,lombok,validation等等),完善的注解,统一的代码规范等等。还有的,就是java语言的诸多高级特性(lambda,stream,io等)。
java语言中,有三个特性,是高级工程师不可或缺的:
- 注解
- 反射
- 泛型
如果代码中,存在这些东西,那么即使应用得还不够合理,也能够从侧面证明这位程序员的技术追求。
这三点是初级工程师很难掌握的,因为缺乏了解与需求(或者说想不到对应的需求)。而高级工程师为了给出更加具有通用性,业务无侵入的代码,就常常需要与这些特性打交道。
在不断积累后的今天,我觉得我可以尝试写一写自己对这些特性的认识了。
今天就从注解开始,阐述我对高级工程师的一些编码认识。
简介
我发现很多小伙伴总是在喜欢记忆一些注解的功能,比如表示非空的@notnull等。
这里,我要从功能与原理角度说明两点:
- 功能:注解是一种“增强型”的注释。只不过相对于只能给人看的注释,注解可以给电脑(jvm,程序等)看。
- 原理:注解的底层是annotation接口的继承者。只不过相对于日常使用的接口,注解需要使用@interface,但是编译的结果依旧是接口继承(如testannotation extend annotation)。
请大家牢记上面两点,这是有关注解认识的绝对核心。
只要大家抓住这两个角度去认识注解,那么很快就可以成为注解达人。后续很多阐述都会从这两个角度,去为大家解释。如为什么人们常说注解是无法继承的,为什么需要元注解等等。
注解的目录结构
其实可以看到,jdk中有关注解的内容很少,非常适合作为三大特性的入门啊。因为注解的实现基础是存在于jvm中的,jdk只是提供了对应的工具。
annotation接口
上面提到注解的底层是接口,这里以图为证。
注意,仔细看这个接口的注释。注释中明确提出,虽然注解的本质是接口。但是直接引用annotation接口,是无法实现注解功能的。
元注解
简介
通俗来说,元注解就是注解的注解。
首先元注解,是java自带的预置注解。从这个角度,需要与@xxx修饰的自定义注解进行区分。
站在功能上来说,元注解就是专门修饰注解的“注释”,用来告诉编译器,虚拟机,相关的信息(如运行时间,目标对象)。
站在原理上来说,元注解也是注解,也是使用了@interface(底层依旧是继承annotation接口)。
不过注解,在底层实现已经继承annotation接口,那么就无法通过继承接口的方式(java不支持多重继承),来保存元注解的信息(尤其这个信息往往不止一类)。那么注解的元注解信息是如何保存,并交给计算机的呢?答案就是通过runtimevisibleannotations进行相关信息的保存的。以下就是对dynamicpropertyverification注解反编译的结果,重点在于反编译结果的最后一段。
classfile /d:/idea_project/ideaprojects/learning/demo/target/classes/tech/jarry/learning/demo/common/anno/dynamicpropertyverification.class last modified apr 12, 2020; size 899 bytes md5 checksum 72657e8b89f0de070bf7085b0dd975da compiled from "dynamicpropertyverification.java" public interface tech.jarry.learning.demo.common.anno.dynamicpropertyverification extends java.lang.annotation.annotation minor version: 0 major version: 52 flags: acc_public, acc_interface, acc_abstract, acc_annotation constant pool: #1 = class #28 // tech/jarry/learning/demo/common/anno/dynamicpropertyverification #2 = class #29 // java/lang/object #3 = class #30 // java/lang/annotation/annotation #4 = utf8 message #5 = utf8 ()ljava/lang/string; #6 = utf8 annotationdefault #7 = utf8 property verification fail #8 = utf8 groups #9 = utf8 ()[ljava/lang/class; #10 = utf8 signature #11 = utf8 ()[ljava/lang/class<*>; #12 = utf8 payload #13 = utf8 ()[ljava/lang/class<+ljavax/validation/payload;>; #14 = utf8 sourcefile #15 = utf8 dynamicpropertyverification.java #16 = utf8 runtimevisibleannotations #17 = utf8 ljava/lang/annotation/documented; #18 = utf8 ljava/lang/annotation/target; #19 = utf8 value #20 = utf8 ljava/lang/annotation/elementtype; #21 = utf8 field #22 = utf8 ljava/lang/annotation/retention; #23 = utf8 ljava/lang/annotation/retentionpolicy; #24 = utf8 source #25 = utf8 ljavax/validation/constraint; #26 = utf8 validatedby #27 = utf8 ltech/jarry/learning/demo/common/anno/dynamicpropertyverificationvalidator; #28 = utf8 tech/jarry/learning/demo/common/anno/dynamicpropertyverification #29 = utf8 java/lang/object #30 = utf8 java/lang/annotation/annotation { public abstract java.lang.string message(); descriptor: ()ljava/lang/string; flags: acc_public, acc_abstract annotationdefault: default_value: s#7 public abstract java.lang.class<?>[] groups(); descriptor: ()[ljava/lang/class; flags: acc_public, acc_abstract annotationdefault: default_value: []signature: #11 // ()[ljava/lang/class<*>; public abstract java.lang.class<? extends javax.validation.payload>[] payload(); descriptor: ()[ljava/lang/class; flags: acc_public, acc_abstract annotationdefault: default_value: []signature: #13 // ()[ljava/lang/class<+ljavax/validation/payload;>; } sourcefile: "dynamicpropertyverification.java" runtimevisibleannotations: 0: #17() 1: #18(#19=[e#20.#21]) 2: #22(#19=e#23.#24) 3: #25(#26=[c#27])
最后一段,通过runtimevisibleannotations,保存了所需要的元注解信息。
如果对jvm底层原理有了解的小伙伴,应该对runtimevisibleannotations不陌生。不了解的小伙伴,可以查看class runtimevisibleannotations
预置注解
元注解
元注解是java自带的,主要分为:
- @rentention:表示目标注解的保持策略。其value为retentionpolicy。如果目标注解没有使用该注解,则默认使用retentionpolicy.class
- @target:表示目标注解的应用目标类型。其value为elementtype。如果目标注解没有使用该注解,则目标注解可以用于除了type_parameter和type_use以外的任何地方(这两个类型都是java8新添加的)。
- @documented:表示目标注解可以出现在javadoc中。
- @repeatable:表示目标注解可以在同一位置,重复使用。
- @inherited:表示目标注解可以随着所修饰的类的继承关系,被子类继承。
@retention
源码:
@documented @retention(retentionpolicy.runtime) @target(elementtype.annotation_type) public @interface retention { /** * returns the retention policy. * @return the retention policy */ retentionpolicy value(); }
通过retentionpolicy枚举表示目标注解的保持策略。
public enum retentionpolicy { /** * 目标注解会在编译期丢失 */ source, /** * 默认行为。虽然目标注解会通过编译,保存至.class文件中,但是jvm不会在运行时识别该注解。 */ class, /** * 常用行为。目标注解会保存至.class文件中,jvm会在运行时识别,并记录该注解。所以可以通过反射获取对应的信息。 * 详见 java.lang.reflect.annotatedelement */ runtime }
为了便于大家理解,这里再举一些例子。这里挑选一些java自带的,不用大家再去自己写demo,增加认知负荷:
- @retention(retentionpolicy.source):如@override注解,由于该注解只是用于进行代码检测,所以只要存在于源码中即可,故选择retentionpolicy.source。类似的还有@suppresswarnings注解等。
- @retention(retentionpolicy.class):涉及注解处理器,所以实例很少。可以查看自定义注解之编译时注解(retentionpolicy.class)(一)。
- @retention(retentionpolicy.runtime):如@deprecated,由于该注解需要在运行时提示用户注解修饰的方法,类等已经过时,所以需要jvm中有对应“注释”信息,故采用retentionpolicy.runtime。类似的还有@repeatable等。
@target
@documented @retention(retentionpolicy.runtime) @target(elementtype.annotation_type) public @interface target { /** * returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ elementtype[] value(); }
通过elementtype枚举表示目标注解的应用目标类型。
public enum elementtype { /** 类,接口(包括注解,即annotation接口),或者枚举类型 */ type, /** 属性 (包括枚举常量,枚举常量示例:retention.source) */ field, /** 方法 */ method, /** 形参(形式参数) */ parameter, /** 构造器 */ constructor, /** 本地变量 */ local_variable, /** 注解类型 */ annotation_type, /** 包 */ package, /** * 类型参数(针对数据类型) * @since 1.8 */ type_parameter, /** * 类型(功能域包含parameter与type_parameter) * @since 1.8 */ type_use }
这里不会一一举例,只会点出重点:
- type_parameter与type_use是java8新增加的。所以使用java7的小伙伴要注意。
- elementtype.type涵盖范围很广泛,在不知用哪个时,可以先用这个。
@documented
默认情况下,注解是不出现在 javadoc 中的。通过给目标注解加上 @documented 元注解,能使目标注解出现在 javadoc 中。
从源码可以看出,@documented是一个没有任何成员的标记注解。
@repeatable
@repeatable注解的使用,引用一个不错的。
package com.zejian.annotationdemo; import java.lang.annotation.*;/** * created by zejian on 2017/5/20. */ @target({elementtype.type,elementtype.field,elementtype.method}) @retention(retentionpolicy.runtime) @repeatable(filterpaths.class) public @interface filterpath { string value(); } @target(elementtype.type) @retention(retentionpolicy.runtime) @interface filterpaths { filterpath[] value(); } @filterpath("/web/update") @filterpath("/web/add") @filterpath("/web/delete")
上述代码,其实分为两个部分:
- 使用@repeatable注解,使得其修饰的@filterpath,可以在目标上重复标记(便于设置不同的成员变量)。
- 通过@filterpaths注解(包含成员变量-filterpath[] value ()),将@filterpath集中到@filterpaths中,便于后续逻辑处理。
@inherited
@inherited同样是只能修饰注解的元注解,它所标注的目标注解具有继承性。
这里解释一下这个继承性,这并不是注解间的继承。而是指目标注解可以随着类的继承,而被子类继承。简单说,就是目标注解修饰的类,其后代类也会被该注解标注(可以通过getannotation方法获取)。
这里不再赘述,感兴趣的小伙伴,可以查看java 注解(annotation)中的相关示例。
功能注解
java预置的功能注解,主要分为:
- @override:该注解修饰的目标方法,必须是重写基类方法,或实现对应接口方法,否则编译器会报错。
- @deprecated:该注解修饰的目标,表示已经过时,不推荐使用。编码时,使用该注解的目标,会有划线提示。
- @suppresswarnings:该注解修饰的目标,将会忽略某些异常(由注解的value指定),从而通过编译器编译。
- @safevarargs:该注解修饰的构造函数(只能修饰构造函数),将会忽略可变参数带来的警告。该注解于java7引入。
- @functionalinterface:该注解修饰的接口,为函数式接口。如java.util.function下的consumer
接口,作为一个函数式接口,被该注解修饰(函数式接口不一定有该注解修饰,但被该注解修饰的接口,一定是函数式接口)。
自定义注解
到了这里,大家应该对注解不再陌生了。
而在日常开发中,我们常常需要自定义开发一些注解。
自定义注解分为以下步骤:
- [必选] 使用@interface来构建自定义注解。一般在创建自定义注解的同时,就达成了该要求。
- [可选] 使用@target元注解。通过该注解,确定自定义注解的作用目标类型。注意:如果目标注解没有使用该注解,则目标注解可以用于除了type_parameter和type_use以外的任何地方(这两个类型都是java8新添加的)。
- [可选] 使用@retention元注解。通过该注解,明确自定义注解的生命周期,或者说自定义注解作用域。如果目标注解没有使用该注解,则默认使用retentionpolicy.class
- [可选] 添加成员变量。格式为“long value() default 1000l;”,与java8的接口成员变量非常类似。注意:注解的成员变量只能采用无参方法表示。并且注解的成员变量,只能采用基本数据类型(char,boolean,byte、short、int、long、float、double)和string、enum、class、annotations数据类型,以及这一些类型的数组。
- [可选] 使用自定义注解。自定义注解的使用领域很多,主要分为两个方向:
- 利用已有框架,不需要自己实现相关逻辑,自定义注解多作为标记注解。如配合springboot的注解,形成自己的注解(相关的逻辑由springboot自己处理)
- 利用已有框架,需要自己实现部分逻辑(不涉及反射),但需要关联已有框架,并实现对应接口。如validation框架的自定义校验注解,感兴趣的小伙伴,可以查看我之前写的validation框架的应用。
- 可选择已有框架,需要自己实现诸多逻辑。如在aop中,我们常常需要通过反射,获取自定义注解的信息(如参数等),或者自定义注解修饰的目标的信息(如参数,方法名等)。这部分,我会在后续的反射部分详细说明。
总结
简单总结一下,本文主要描述了:
- 注解是什么:增强型的注释,本质是接口
- 元注解是什么:注解的注解,作用是为了标识目标注解。包括@target,@retention,@documented,@repeatable,@inherited.
- 预置注解是什么:jdk自带的经典功能注解,如@override,@deprecated,@suppresswarnings,@safevarargs,@functionalinterface。
- 自定义注解如何实现:主要分为五步,但是其中必要的步骤,就一步:使用@interface来构建自定义注解。
至此,java注解的内容就基本展现了。
最后,还是强调两个方面:
- 注解就是增强型的注释(可被计算机识别的注释),本质是接口。把握住这两点,就非常好理解注解与它的各种规则,行为。
- 注解本身并没有任何功能(因为它只是注释,本质也只是接口),需要其他代码支撑,它才能体现价值。
希望对大家有所帮助,还有不清楚的地方,可以查看下列参考目录。
愿与诸君共进步。
附录
参考
上一篇: 后端前端联调须知
下一篇: 断点调试获取程序当前位置的运行结果