欢迎大家来到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,Class、Method、Field 等都是 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,所以,标记在 Method、Field 等位置也同样是无效的。
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() 方法可以。
待思考的问题
当一个注解的 @Target 为 ElementType.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 拥有 value 及 packages 两个方法,假设 packages 方法用于设置需要扫描的包,而注解 B 又标记在注解 C 上,本意是想注解 C 可以结合注解 B 的功能,但是,使用注解 C 进行标记时,注解 C 才是根注解,并不能设置 packages 方法,而注解 B 的 packages 已经被写死了,这就意味着使用注解 C 时,其元注解的属性都是固定值,这明显限制了组合注解的能力。
写在最后的话:关注一下,( ⊙ o ⊙ )!!!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/120579.html