开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
Java注解(Annotation)是JDK 5.0中引入的一种机制,用于对类,方法,参数和包等进行标注,Java中常见的@Override,@Deprecated等标识就是注解。
一些常见注解定义如下所示:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
@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();
}
可以看出注解是一个使用@interface修饰的接口类,在其上方使用@XXX标记,按照用途和使用方法来讲,注解主要分为三类:
-
元注解:元注解指的是为构建注解环境而定义的基础注解,上述代码中的@Documented和@Inherited都是元注解,元注解的@target为ElementType.ANNOTATION_TYPE,基础的元注解有以下几类:
注解名称 注解说明 备注 @Documented 文档类型注解,可以将其标注的元素添加到javadoc中,上述代码中,SafeVarargs,Inherited,Documented均使用了该注解,说明这几个接口都会被添加到javadoc内 / @Inherited Inherited是注解继承的意思,主要说的是当注解A使用@Inherited定义时,B类使用注解A,如果其子类C没有注解时,会自动继承父类的注解,也就是父类和子类的注解一致 / @Repeatable JDK 1.8引入的新的元注解,代表在同一类上,该注解可多次使用 / @Retention 用于定义注解在编译或者运行过程中如何存储,其取值主要包含三类: 1.RetentionPolicy.SOURCE 表示被标记的注解,仅仅只在代码级被保留,编译器检查使用; 2.RetentionPolicy.CLASS 该标记表示被标记的注解会被编译器保留,但仅仅在编译期保留,但是会被JVM忽略; 3.RetentionPolicy.RUNTIME 该标记的注解,表示会被JVM保留,可以在运行时被使用; 一般情况下,我们对Retention使用RetentionPolicy.RUNTIME,注解括号里面传入的就是注解取值,有点类似于函数参数,在Retention的定义中可以看到value()的声明 / @Target Target用于定义注解的作用类型,比如注解只能针对方法使用,类和方法都可以使用等,其取值如下所示: 1. ElementType.ANNOTATION_TYPE 应用于一个注解类型; 2. ElementType.CONSTRUCTOR 应用于构造函数 3. ElementType.FIELD 应用于类的成员变量 4. ElementType.LOCAL_VARIABLE 应用于局部变量 5. ElementType.METHOD 应用于方法 6. ElementType.PACKAGE 应用于包 7. ElementType.PARAMETER 应用于方法的参数 8. ElementType.TYPE 应用于类 / -
JDK内置注解
JDK内置注解说的是一些Java内部以定义的注解类型,如@Override等,JDK已定义的注解如下所示:
注解名称 说明 存储方式 作用域 是否可重复声明 是否可继承 备注 @Override 标记该方法是重写父类方法 RetentionPolicy.SOURCE,编译时 方法 否 否 / @Deprecated 标记被弃用,后续不再维护 RetentionPolicy.RUNTIME,运行时 所有Target类型均可用 否 否 / @SuppressWarnings 用于抑制编译器警告,使用SuppressWarnings修饰后,编辑器不会再提示相关告警,其取值为String类型,常见用法为@SuppressWarnings("unchecked") RetentionPolicy.SOURCE,编译时 类元素,成员变量,方法,参数,构造器,局部变量 否 否 / @SafeVarargs JDK 7 专门为抑制堆污染警告提供的 RetentionPolicy.RUNTIME,运行时 构造器,方法 否 否 / @FunctionalInterface Java 8中新增的函数时接口,如果接口中只有一个抽象方法(允许包含多个default或static方法),则该接口被称为函数式接口 RetentionPolicy.RUNTIME,运行时 类元素 否 否 / -
自定义注解
自定义注解指的是开发者根据自己需求,自己使用元注解创建的注解类,自定义注解往往用于实现特定功能,比如Dagger2和Hilt,其内部注解可以帮助完成依赖注入过程,Arouter中的注解用于实现路由协议等
自定义注解实现流程
一般情况下,自定义注解需要三步:
- 使用元注解定义注解,类似于上文中代码
- 在需要注解的地方声明注解
- 解析注解
接下来我们来按照步骤定义一个在函数上使用的运行时注解,该注解接受字符串作为取值。
使用元注解定义注解
import java.lang.annotation.*;
// 可以加入javadoc中
@Documented
// 运行时可以使用的注解
@Retention(RetentionPolicy.RUNTIME)
// 注解作用在方法上
@Target(ElementType.METHOD)
public @interface HelloAnnotation {
/**
* 注解取值,返回String字符串
*/
String value();
}
按照上文知识及我们的目标定义注解如上所示
声明注解
创建TestHelloAnnotation.java类,在其内部定义函数,使用@HelloAnnotation注解,代码如下:
public class TestHelloAnnotation {
@HelloAnnotation("this is hello method annotation value")
public void hello() {
}
}
解析注解
对于注解而言,在获取其值或者调用注解函数时,需要使用反射执行,注解不同于普通java类,自定义注解更多的用来解决一些机械化的工作,通常情况下搭配APT(Annotation Processor Tools)以及gradle插件使用。
我们在main函数中通过反射获取TestHelloAnnotation中hello函数的注解,代码如下:
Class testHelloAnnotationClass = TestHelloAnnotation.class;
// 获取hello函数
Method helloMethod = testHelloAnnotationClass.getMethod("hello");
// 判断hello函数是否用HelloAnnotation修饰
if (helloMethod.isAnnotationPresent(HelloAnnotation.class)) {
// 获取hello函数的注解对象
HelloAnnotation helloAnnotation1 = helloMethod.getAnnotation(HelloAnnotation.class);
// 打印hello函数注解对象的value取值
System.out.println(helloAnnotation1.value());
}
运行结果如下:
注解+APT在各大开源库中有较多应用,需要重视,比如ButterKnife,Arouter,Dagger2,Hilt等,都有注解的影子
Retention三种类型的应用场景及区别
| Retention | 级别 | 技术 | 说明 | 典型示例 |
|---|---|---|---|---|
| RetentionPolicy.SOURCE | 保留在源代码 | APT | 在编译期获取注解以及使用注解的类的所有信息,一般用于做编译时检查或者生成辅助类,信息收集等 | ButterKnife,ARouter,Tinker等 |
| RetentionPolicy.CLASS | 保留在class文件中,JVM忽略 | 字节码增强 | 在编译出class文件后,通过修改class文件实现修改代码逻辑的目的,通过与ASM等结合使用 | AspectJ,Roubust |
| RetentionPolicy.RUNTIME | 保留在class文件中,可运行时访问 | 反射 | 在运行时,可以通过反射获取类的注解和元素,以完成不同的代码逻辑 | / |