注解编程 | 自定义注解

注解编程 | 自定义注解注解的概念注解 Annotation 从 JDK1

欢迎大家来到IT世界,在知识的湖畔探索吧!

注解编程 | 自定义注解



欢迎大家来到IT世界,在知识的湖畔探索吧!

注解的概念

注解(Annotation)从 JDK1.5 开始引入,具有以下几个特点

  • 注解是一种元数据形式,即注解属于 JAVA 的一种数据类型,和类、接口、数组、枚举类似
  • 注解用来修饰类、方法、变量、参数、包
  • 注解不会对所修饰的对象产生直接的影响

如何自定义注解

在学习如何自定义注解之前,先学习一下元注解的概念,因为要自定义注解,必须要使用元注解。

元注解

元注解(Meta-Annotation)的作用就是负责修饰其他注解,JDK 提供了如下几个标准的元注解。

@Target

@Retention

@Documented

@Inherited

@Repeatable

  • @Target

@Target 元注解用于声明注解可以修饰的对象范围,有的注解只能使用的方法上(比如 @Override),有的注解却可以使用在类上(比如 @Deprecated),就是因为这些注解在声明时设置了相应的 @Target 范围。

@Target 可设置多个,类型为 ElementType 数组:

类型

可注解元素

TYPE

类、接口、枚举、注解

FIELD

属性

METHOD

方法

PARAMETER

方法参数

CONSTRUCTOR

构造方法

LOCAL_VARIABLE

局部变量

ANNOTATION_TYPE

注解

PACKAGE

TYPE_PARAMETER

类型参数(即泛型)

TYPE_USE

任何类型

注解的属性类型为数组时,如果需要设置多个值,使用大括号进行设置 @Target = { TYPE, FIELD },如果只有一个值,则可以省略大括号 @Target = TYPE

  • @Retention

注解也有生命周期,有的注解只出现在源代码中,在编译阶段会被编译器忽略(比如 @Override 纯粹用于规范语法),有的注解会被编译进 .class 文件中,但类加载过程中会被虚拟机忽略,而更多的注解则需要被虚拟机加载到内存。

@Retention 元注解用于声明注解的生命周期,只能设置一个,类型为 RetentionPolicy

类型

生命周期

描述

SOURCE

源码阶段

只出现在源代码中,在编译阶段会被编译器忽略

CLASS

编译阶段

会被编译进 .class 文件中,类加载过程中会被虚拟机忽略

RUNTIME

运行期阶段

会被虚拟机加载到 Class 对象中,可以通过反射进行得到这个注解

  • @Documented

一个注解使用 @Documented 元注解进行修饰,则该注解会出现在 JavaDoc 文档中。@Documented 元注解是一个标记注解,没有属性需要设置。

  • @Inherited

JAVA 中的继承是经常被用到的,如果一个注解标记在父类上,通过子类是否可以得到该注解?

正常是得不到的,但如果注解上使用了 @Inherited 元注解进行修饰,则可以得到。

@Inherited 的继承功能只对 TYPE 类型中的 class 有效,如果注解使用在接口(interface)、方法(method)、属性(field)等,即便注解上声明了 @Inherited,也无法通过子类获取。

  • @Repeatable

@Repeatable 元注解用于声明注解可以重复使用,正常情况下,一个元素上只能标记一个同类型注解,使用了 @Repeatable 则可以同时标记多个。

自定义注解

注解跟类、接口一样,也是 JAVA 一种数据类型,声明注解跟声明类,接口差不多,只不过类型为 @interface。另外,前面学习了元注解,需要为注解标记元注解声明注解的作用范围及生命周期。

@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Version { }

欢迎大家来到IT世界,在知识的湖畔探索吧!

注解也支持方法,但注解的方法格式跟类有点不一样

  • 注解跟接口一样,方法的默认修饰符为 public,可以省略不写
  • 方法返回类型只能使用八种基本数据类型(byte、short、char、int、long、float、double、boolean)、String、Enum、Class、Annotation 以及前面几种类型的数组类型
  • 方法可以设置默认值,没有设置默认值的方法,在使用时必须设置值
  • value 是一个特殊的方法,如果注解只有一个方法 value 需要设置值,在使用时可以不用带方法名(见下面的示例),所以如果注解只有一个方法,建议使用方法名 value

没有方法的注解称之为标记注解

欢迎大家来到IT世界,在知识的湖畔探索吧!@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Version { // 使用 default 关键字设置默认值 String value() default ""; } // 下面两行代码的效果是一样的 // @Version(value = "1.0.0") @Version("1.0.0") public class AnnotationTest() { }
注解编程 | 自定义注解

如何使用注解

在注解的概念中,我们提到 注解不会对所修饰的对象产生直接的影响,那使用了注解到底有什么用呢?

@Retention 设置为 RUNTIME 时,注解会被加载到 Class 对象中,通过反射就可以得到注解以及注解的属性值,从而实现对注解的解析。

设置 @Retention 为 SOURCE 及 CLASS 注解,并不会加载到内存,不是代码层面可以操作的,这里只讨论 RUNTIME 如何使用。

AnnotatedElement 接口提供了获取注解的 API,ClassMethodField 等都是 AnnotatedElement 接口的实现类。

  • isAnnotationPresent(Class<?> var1):判断当前元素是否存在指定注解,支持 @Inherited
  • getAnnotation(Class<T> var1):获取当前元素的指定注解,支持 @Inherited
  • getAnnotations():获取当前元素的所有注解,支持 @Inherited
  • getAnnotationsByType(Class<T> var1):获取当前元素的指定类型注解,支持 @Inherited@Repeatable
  • getDeclaredAnnotation(Class<T> var1):获取当前元素的指定注解,仅限标注在当前元素上的注解
  • getDeclaredAnnotations():获取当前元素的所有注解,仅限标注在当前元素上的注解
  • getDeclaredAnnotationsByType(Class<T> var1):获取当前元素的指定类型注解,仅限标注在当前元素上的注解,支持 @Repeatable

通过反射得到注解

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Version { String value() default ""; } @Version("1.0.0") public class AnnotationTest() { public static void main(String[] args) { // 通过 Class 对象得到 Annotation A annotation = AnnotationTest.class.getDeclaredAnnotation(A.class); // 通过 Annotation 得到 value 属性值 System.out.println(annotation.value()); } }

通过反射得到元注解

元注解是声明在注解上的注解,要想得到元注解,就必须先得到注解,注解本身也是一个 Class,通过解析注解类就可以得到元注解,但这里与解析普通的 Class 有一点点不一样,需要使用 Annotation.annotationType() 方法而不是 Object.getClass() 方法获取类。

欢迎大家来到IT世界,在知识的湖畔探索吧!@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Version { String value() default ""; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Version public @interface VersionRoute { String value() default ""; } @VersionRoute("1.0.0") public class AnnotationTest { public static void main(String[] args) { VersionRoute annotation = AnnotationTest.class.getDeclaredAnnotation(VersionRoute.class); // 通过注解的 annotationType() 方法得到 Class Version versionAnno = annotation.annotationType().getDeclaredAnnotation(Version.class); System.out.println(versionAnno); } }

@Inherited 的用法

不声明 @Inherited 元注解的情况下,将注解标记到父类上,通过子类进行解析

// 注解标记在父类上 @Version("P") public static class PInfo {} public class AnnotationTest extends PInfo { public static void main(String[] args) { // 通过子类判断父类上的注解 boolean present = AnnotationTest.class.isAnnotationPresent(Version.class); System.out.println("isAnnotationPresent:" + present); Version version = AnnotationTest.class.getAnnotation(Version.class); System.out.println("getAnnotation:" + version); version = AnnotationTest.class.getDeclaredAnnotation(Version.class); System.out.println("getDeclaredAnnotation:" + version); } }

输出结果如下,可以看到注解标记在父类上,通过子类进行判断是完全无感知的

欢迎大家来到IT世界,在知识的湖畔探索吧!isAnnotationPresent:false getAnnotation:null getDeclaredAnnotation:null

Version 添加 @Inherited 元注解

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Version { String value() default ""; }

再次执行上面的程序,输出结果如下,可以看到 getAnnotation 可以到得父类上的注解,而 getDeclaredAnnotation 方法则无法获取。

欢迎大家来到IT世界,在知识的湖畔探索吧!isAnnotationPresent:true getAnnotation:@org.springframework.core.annotation.AnnotationTest$Version(value=B) getDeclaredAnnotation:null

PInfo 类改为接口,再次执行,输出结果如下,可以看到虽然注解声明了 @Inherited,但是标记在接口上也是不启作用的,因为 @Inherited 只支持 TYPE 范围的 class,所以,标记在 MethodField 等位置也同样是无效的。

isAnnotationPresent:false getAnnotation:null getDeclaredAnnotation:null

@Repeatable 的用法

为注解声明 @Repeatable 元注解,需要设置一个注解类,这个注解类的 value 方法的返回值必须是当前注解的数组类型,说起来有点绕,直接通过代码看比较清楚。

欢迎大家来到IT世界,在知识的湖畔探索吧!@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Versions.class) public @interface Version { String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Versions { Version[] value(); }

标记一个 @Version 注解

@Version("P") public class PInfo {} public class AnnotationTest { public static void main(String[] args) { // 解析 Version 注解 boolean present = PInfo.class.isAnnotationPresent(Version.class); System.out.println("isAnnotationPresent:" + present); Version vAnno = PInfo.class.getDeclaredAnnotation(Version.class); System.out.println("getDeclaredAnnotation:" + vAnno); Version[] vAnnos = PInfo.class.getDeclaredAnnotationsByType(Version.class); System.out.println("getDeclaredAnnotationsByType:" + Arrays.toString(vAnnos)); // 解析 Versions 注解 present = PInfo.class.isAnnotationPresent(Versions.class); System.out.println("isAnnotationPresent:" + present); Versions vsAnno = PInfo.class.getDeclaredAnnotation(Versions.class); System.out.println("getDeclaredAnnotation:" + vsAnno); Versions[] vsAnnos = PInfo.class.getDeclaredAnnotationsByType(Versions.class); System.out.println("getDeclaredAnnotationsByType:" + Arrays.toString(vsAnnos)); } }

输出结果如下,可以看到当只标记了一个 @Version 注解时,@Versions 是不存在的

欢迎大家来到IT世界,在知识的湖畔探索吧!isAnnotationPresent:true getDeclaredAnnotation:@Version(value=P) getDeclaredAnnotationsByType:[@Version(value=P)] isAnnotationPresent:false getDeclaredAnnotation:null getDeclaredAnnotationsByType:[]

标记多个 @Version 注解,再次执行上面的程序

@Version("B") @Version("P") public class PInfo {}

输出结果如下,可以看到,标记多个 @Version 注解与标记一个 @Version 标记的解析结果完全不一样

欢迎大家来到IT世界,在知识的湖畔探索吧!isAnnotationPresent:false getDeclaredAnnotation:null getDeclaredAnnotationsByType:[@Version(value=P), @Version(value=B)] isAnnotationPresent:true getDeclaredAnnotation:@Versions(value=[@Version(value=P), @Version(value=B)]) getDeclaredAnnotationsByType:[@Versions(value=[@Version(value=P), @Version(value=B)])]

标记一个注解时,与使用没有声明 @Repeatable 的注解是一样的。标记多个注解时,通过 getDeclaredAnnotation(Version.class) 居然获取不到注解,反而 getDeclaredAnnotation(Versions.class) 可以获取到。这是因为 @Repeatable 元注解最终解析成

@Versions({ @Version("P"), @Version("B") }) public class PInfo {}

虽然多注解时,getDeclaredAnnotation() 无法获取到 Version 注解,但
getDeclaredAnnotationsByType()
方法可以。

注解编程 | 自定义注解

待思考的问题

当一个注解的 @TargetElementType.TYPE 或者 ANNOTATION_TYPE 时,就可以声明在其它注解之上,形成组合注解。一个注解上可以声明多个元注解,元注解上还可以声明元注解,可以形成非常复杂的组合注解。声明在其它注解之上的注解称之为元注解,而最终可以标记在某个元素之上的称之为根注解。

假设有如下三个注解形成的组合注解 C,C 为根注解,A 及 B 都是元注解。

欢迎大家来到IT世界,在知识的湖畔探索吧!@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface A { String value() default ""; String name(); } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @A(value = "A", name = "Hello") public @interface B { String value() default ""; String packages(); } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @B(value = "B", packages = "World") public @interface C { String value() default ""; } @C("C") public class PInfo {}

如何快速得到注解?

比如,想知道一个元素有没有标记注解 A,注解 A 可能直接标记在元素上,也可能是以组合注解的元注解形式(比如注解 C)标记在元素上,但是,JDK 提供的 API 只能快速得到根注解,怎么样才能快速得到注解,而不用管是根注解还是元注解。

public class AnnotationTest { public static void main(String[] args) { A annotation = getAnnotation(PInfo.class, A.class); System.out.println(annotation); } private static <T extends Annotation> T getAnnotation(AnnotatedElement source, Class<T> annotationClass) { Annotation[] annotations = source.getAnnotations(); T anno = null; for (Annotation annotation : annotations) { System.out.print("检查注解:" + annotation); if (annotation.annotationType().equals(annotationClass)) { return (T) annotation; } else if (annotation.annotationType().getPackage().getName().startsWith("java.")) { System.out.print(" --- 忽略"); System.out.println(); continue; } else { System.out.println(); anno = getAnnotation(annotation.annotationType(), annotationClass); if (anno != null) { System.out.println(); return anno; } } } return null; } }

如何动态为元注解的属性设值?

注解 B 拥有 valuepackages 两个方法,假设 packages 方法用于设置需要扫描的包,而注解 B 又标记在注解 C 上,本意是想注解 C 可以结合注解 B 的功能,但是,使用注解 C 进行标记时,注解 C 才是根注解,并不能设置 packages 方法,而注解 B 的 packages 已经被写死了,这就意味着使用注解 C 时,其元注解的属性都是固定值,这明显限制了组合注解的能力。

写在最后的话:关注一下,( ⊙ o ⊙ )!!!

注解编程 | 自定义注解

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/120579.html

(0)
上一篇 6小时前
下一篇 6小时前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信