注解(annotation)
定义
java中的annotation是 JDK 5.0 后引入的一种注释机制,本身并没有操作代码。注解是元数据的一种形式,提供有关程序但不属于程序本身的数据。简单的说:annotation就是一个标志。
如何使用
@interface就可以定义一个annotation,在此annotation上面可以添加元注解。
public @interface MyAnnotation { }
annotation 默认实现Annotation接口
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
annotation.annotationType()
获取到的是该接注解类型,interface com.xxd.reflect.basic.domain.DescObtain
annotation.getClass()
获取到的是代理类,class com.sun.proxy.$Proxy2
元注解
在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)
- @Target 注解可以打在什么位置上 (jdk14 中 12个位置)
- @Retention 注解保留的时间,3个阶段
- @Documented 注解是否保留到生成的 Doc 文档上
- @Inherited 注解是否被继承 (在父类上使用可继承注解,子类能获取到,其它地方,接口、方法、Field等都能继承)
@Target的位置
在 Annotation 上打上 @Target 可以确保 Annotation 不被使用在错误的位置上。
以下是 jdk14 上 target 可以使用的所有位置
public enum ElementType {
/** Class, interface (including annotation type), enum, or record
* declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE,
/**
* {@preview Associated with records, a preview feature of the Java language.
*
* This constant is associated with <i>records</i>, a preview
* feature of the Java language. Programs can only use this
* constant when preview features are enabled. Preview features
* may be removed in a future release, or upgraded to permanent
* features of the Java language.}
*
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 14
*/
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
essentialAPI=true)
RECORD_COMPONENT;
}
详细说明
类型 | 使用位置说明 |
---|---|
TYPE | 接口、类、注解、枚举 |
FIELD | 成员变量 |
METHOD | 方法 |
PARAMETER | 方法参数 (不包含泛型参数) |
CONSTRUCTOR | 构造函数 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解 |
PACKAGE | package-info.java,只能使用在此文件上 |
_TYPE_PARAMETER ( _Since: 1.8 ) | 泛型上 (如 T,U之前) |
_TYPE_USE ( _Since: 1.8 ) | 一个比较特殊的注解,可以使用在一切可以使用的字段上 (可以使用就是指 类、接口、成员变量、参数、泛型等可以动态传入参数的地方)。方法是固定,不存在动态方法,所以不能使用此注解,类似的还有 package-info.java,module-info.java 等。一般用来做检测,如 @NonNull |
_MODULE ( _Since: 1.8 ) | module-info.java,只能使用在此文件上 |
RECORD_COMPONENT | 将会被丢弃,无法测试 |
不使用Target | 可以用于绝大部分地方,除了 泛型、泛型限定符、module-info.java |
@Retention保留级别
3种级别的保留时间 ( 不使用 Retention 将保留为 class级别)
- RetentionPolicy.SOURCE 标记的注解仅保留在源级别中,并被编译器忽略
- RetentionPolicy.CLASS 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略
- RetentionPolicy.RUNTIME 标记的注解由 JVM 保留,因此运行时环境可以使用它 | 级别 | 技术 | 说明 | | —- | —- | —- | | 源码 | APT | 在编译期能够获取注解与注解声明的类包括类中所有成员信一般用于生成额外的辅助类。 | | 字节码 | 字节码增强 | 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。 | | 运行时 | 反射 | 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。 |
Annotation的实际应用
APT
RetentionPolicy.SOURCE 注解主要用法之一
APT全称为:”Anotation Processor Tools”,意为注解处理器。顾名思义,其用于处理注解。编写好的Java源文件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME 包含 SOURCE 、CLASS。
APT 入门写法步骤
新建一个java module ,写一个类继承AbstractProcessor
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE,"!!!!!!!!!!!!");
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() { // 支持的版本,必须让支持到最新版本
return SourceVersion.latestSupported();
}
}
写上你需要处理的Annotation全类名,否则找到的set为null ```java // 需要处理的注解全类名 @SupportedAnnotationTypes(“com.xxd.apt.annotation.AptAnnotation”) public class MyProcessor extends AbstractProcessor {
}
3. 注册你的Processor,就像Android xml中注册activity一样,需要注册Processor才能找到它。切换到Project目录下,在main目录下新建 resource -> META-INF -> services -> javax.annotation.processing.Processor 一个字符都不能错,这个固定的路径写法,gradle 3.0之后不支持自动生成,所以最好手动写
<br />该文件下放上你的需要给javac处理的processor,例如 com.xxd.apt.processor.MyProcessor
4. 在需要使用APT的工程下引入注解注处理器
`annotationProcessor project(':apt-processor')`
下面是一个获取到具体的列子
```java
// 需要处理的注解全类名
@SupportedAnnotationTypes("com.xxd.apt.annotation.AptAnnotation")
public class MyProcessor extends AbstractProcessor {
private Messager messager;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager = processingEnv.getMessager();
printMessage("开始处理");
annotations.forEach(a -> {
printMessage("Set<? extends TypeElement> annotations 集合之一: " + a.getSimpleName().toString());
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(a);
elementsAnnotatedWith.forEach(element -> {
// 获取到了所有使用的地方
printMessage(element.toString());
AptAnnotation aptAnnotation = element.getAnnotation(AptAnnotation.class);
printMessage(String.format(Locale.getDefault(), "当前AptAnnotation的key=%s, value=%s", aptAnnotation.key(), Arrays.toString(aptAnnotation.value())));
});
});
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() { // 支持的版本,必须让支持到最新版本
return SourceVersion.latestSupported();
}
private void printMessage(String info) {
if (messager != null)
messager.printMessage(Diagnostic.Kind.NOTE, info);
}
}
Task :annotation:classes 注: 开始处理 注: Set<? extends TypeElement> annotations 集合之一: AptAnnotation 注: com.xxd.annotation.apt.AptTest 注: 当前AptAnnotation的key=xxd, value=[2, 3, 5] 注: main(java.lang.String[]) 注: 当前AptAnnotation的key=zl, value=[1, 5, 11] 注: 开始处理
tips : 获取这些元素后就可以拼装.java文件了,使用JavaPoet 效率更高
IDE语法检查
RetentionPolicy.SOURCE 注解主要用法之二(Android 程序中的语法检查,不是java中的类)
public static void main(String[] args) {
setBehavior(222);
}
private static final int BEHAVIOR_SHOW = 1;
private static final int BEHAVIOR_HINT = 2;
private static final int BEHAVIOR_RESUME = 3;
/**
* 我想要的是传入上面3种 behavior 模式之一,但是int可以随便传
* @param behavior
*/
private static void setBehavior(int behavior){
}
现在使用Android中的 IntDef 限制
@IntDef({Test3.BEHAVIOR_SHOW, Test3.BEHAVIOR_HINT, Test3.BEHAVIOR_RESUME})
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
@interface Test3Behavior {
}
public static void main(String[] args) {
// setBehavior(1); 不能再随便传入值了
setBehavior(BEHAVIOR_SHOW);
}
@Test3Behavior // 也可以不加此注解,加了就必须写到 @IntDef 上,强制使用
private static final int BEHAVIOR_SHOW = 1;
@Test3Behavior
private static final int BEHAVIOR_HINT = 2;
@Test3Behavior
private static final int BEHAVIOR_RESUME = 3;
/**
* 我想要的是传入上面3种 behavior 模式之一,但是int可以随便传
*
* @param behavior
*/
private static void setBehavior(@Test3Behavior int behavior) {
}
字节码插桩
RetentionPolicy.CLASS注解主要用法,这里只给出原理,具体用法还没实现。
Android中是在编译成 .class 文件之后,在打包成 .dex文件之前插入,可以做无埋点统计等功能。
反射
RetentionPolicy.RUNTIME注解主要用法
这里非常常用,就不多介绍了,举一个简单的例子
@Retention(RetentionPolicy.RUNTIME)
public @interface ReflectAnnotation {
int value();
}
/**
* 运行时注解测试
* 测试反射
*/
public class RuntimeAnnotationTest {
@ReflectAnnotation(30)
private int i;
private int j;
public static void main(String[] args) {
Class<RuntimeAnnotationTest> runtimeTestClass = RuntimeAnnotationTest.class;
Field[] fields = runtimeTestClass.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
ReflectAnnotation annotation = field.getAnnotation(ReflectAnnotation.class);
if (annotation != null) {
field.setAccessible(true);
int value = annotation.value();
System.out.println("当前ReflectAnnotation的数值是:" + value);
// 可以给field 反射赋值等操作,这里不再展示
}
});
}
}
Task :annotation:RuntimeTest.main() 当前ReflectAnnotation的数值是:30