网站首页 > java教程 正文
注解的概念
注解(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 {
}
注解也支持方法,但注解的方法格式跟类有点不一样
- 注解跟接口一样,方法的默认修饰符为 public,可以省略不写
- 方法返回类型只能使用八种基本数据类型(byte、short、char、int、long、float、double、boolean)、String、Enum、Class、Annotation 以及前面几种类型的数组类型
- 方法可以设置默认值,没有设置默认值的方法,在使用时必须设置值
- value 是一个特殊的方法,如果注解只有一个方法 value 需要设置值,在使用时可以不用带方法名(见下面的示例),所以如果注解只有一个方法,建议使用方法名 value
没有方法的注解称之为标记注解
@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() 方法获取类。
@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);
}
}
输出结果如下,可以看到注解标记在父类上,通过子类进行判断是完全无感知的
isAnnotationPresent:false
getAnnotation:null
getDeclaredAnnotation:null
为 Version 添加 @Inherited 元注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Version {
String value() default "";
}
再次执行上面的程序,输出结果如下,可以看到 getAnnotation 可以到得父类上的注解,而 getDeclaredAnnotation 方法则无法获取。
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 方法的返回值必须是当前注解的数组类型,说起来有点绕,直接通过代码看比较清楚。
@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 是不存在的
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 标记的解析结果完全不一样
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 都是元注解。
@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 ⊙ )!!!
- 上一篇: 「干货」如何将反射和自定义注解应用在项目开发中
- 下一篇: java自定义注解,怎么处理边界条件
猜你喜欢
- 2024-09-27 在Spring Boot项目中创建和使用自定义注解
- 2024-09-27 这一篇 Java 注解,写得太好了(java注解使用)
- 2024-09-27 学习廖雪峰的JAVA教程---注解(定义注解@interface)
- 2024-09-27 自定义注解妙用,一行代码搞定用户操作日志记录,你学会了吗?
- 2024-09-27 Spring Boot 整合mybatis,使用注解的方式(自动生成注解)
- 2024-09-27 面试官:实际工作中哪里用到了自定义注解?
- 2024-09-27 使用自定义注解和切面AOP实现Java程序增强
- 2024-09-27 Java-注解有什么用?该怎么用?(java 注解的作用)
- 2024-09-27 Java 17中的元注解:自定义注解的行为
- 2024-09-27 自定义注解你真会用吗?(说说自定义注解的场景及实现)
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)