[toc]
一句话理解
注解(annotation)是一种另类的注释,如果不与其它技术结合,它不会起任何作用;常常结合的技术包括apt(annotation processing tool),asm等;它常常用来改变原有的执行逻辑,比如根据注解修改或新增源码(apt/asm),或者在运行期间与反射结合修改执行逻辑。
一.注解的产生与定义
注解是在jdk 1.5之后引入的一种注释,与普通被虚拟机忽略的注释不一样,注解根据它的作用期不一样,在被注解处理器处理后,会产生不同的效果。它可以被注解在指定的类,方法,字段,参数前。
如果没有注解处理器,它将和普通的注释一样,不起任何作用。
它产生的目的是通过一种简单而直观的方式,在不同阶段影响字节码的运行流程。
1.注解的定义
通过@interface这个标记,我们可以定义一个注解 它包含两个核心的元素:
- 注解的生命周期 @Rentention,用于像编译器告知注解产生作用的时机,它主要包含源码期(.java文件处理阶段),编译器(.class文件处理阶段),运行期(字节码解释运行阶段)
- 注解的作用对象 @Target , 它标记了注解作用的对象,一般包含类,属性,方法,参数以及注解等,标记注解的注解,我们常常称为元注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2.注解与Annotation的关系
测试下定义的注解与Annotation的关系
System.out.println("an=" + an);
System.out.println("an.annotationType=" + an.annotationType());
System.out.println("an.getClass:()=" + an.getClass());
System.out.println("an instanceof Annotation=" + (an instanceof Annotation));
System.out.println("BindView.class=" + OnClick.class);
输出如下:
System.out: an=@com.hch.ioc.BindView(value=[2131231092])
System.out: an.annotationType=interface com.hch.ioc.BindView
System.out: an.getClass:()=class $Proxy1
System.out: an instanceof Annotation=true
System.out: BindView.class=interface com.hch.ioc.BindView
由此可以得出结论:
- 通过@interface定义的注解,它本身是一个Annotation
- 注解的annotationType与通过.class获取的注解类型一致,都是具体的注解类型interface com.hch.ioc.BindView
- 通过getClass得到的并不是Annotation,而是一个代理class对象
二.注解的生命周期
注解的生命周期决定了编译器在哪个阶段去处理注解 通过@Rentention这个元注解,可以定义注解的作用期,对于每个注解,仅能作用于一个作用期,它的定义如下:
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
从生命周期来看, RUNTIME > CLASS > SOURCE
三.注解的作用对象
注解处理器处理时,需要告知得到注解所作用的对象,它通过@Target这个元注解来标注,常用的作用对象包括:
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE , /* 包声明 */
TYPE_PARAMETER , /* 泛型参数申明 */
TYPE_USE , /* 泛型变量使用申明 */
}
其中TYPE_PARAMETER , TYPE_USE 从java 1.8之后引入,用于注解泛型的类型
public class Callback<@NoneNull T> {
...
}
@Target(ElementType.TYPE_PARAMETER)
public @interface NoneNull {}
@Test String text;
@Target(ElementType.TYPE_USE)
public @interface Test {}
四.注解处理器
annotation Processing tool 负责扫描并处理注解。它可以处理处于任何生命周期的注解
使用apt的步骤总结如下:
- 新建Java library ,命名为annotation,用于存放需要处理的注解
- 新建Java library ,命名为annotationProcessor ,用于处理注解,它依赖于annotation library
- 在android app 依赖annotation 和annotationProcessor
下面以BindView 这个注解来举例说明
1.新建 java library库(annotation)
新建流程很简单
graph LR
file-->new_module
new_module --> javalibrary
为什么是java library ,因为annotationProcessor库必须是java库,而java库无法依赖android 库。
添加BindView
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2.新建 java library库(annotationProcessor)
在build.gradle中添加依赖
- auto-service用于自动生成annotationProcessor的描述,并且对不同的注解分别采用step处理,每个step都只需要申明的annotation.
- javapoet 用于生成新的java文件
- 对于annotation library的依赖
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
tasks.withType(JavaCompile){
options.encoding = "UTF-8"
}
dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc3'
implementation "com.google.auto.service:auto-service:1.0-rc3"
implementation "com.google.auto:auto-common:1.1.2"
implementation 'com.squareup:javapoet:1.13.0'
// 依赖于注解
implementation project(':annotation')
}
新建注解处理器HCHProcessor,
- steps方法定义了要处理的注解和实际的处理逻辑。
- @AutoService 会帮助自动生成annotationsProcessor在build/classes/META-INF/services/javax.annotation.processing.Processor中的注解申明内容为com.huya.annotationprocessor.HCHProcessor
- getSupportedSourceVersion 指定要支持的java的源码版本
@AutoService(Processor.class)
public class HCHProcessor extends BasicAnnotationProcessor {
@Override
protected Iterable<? extends Step> steps() {
return ImmutableList.of(new BindViewStep(processingEnv) , new FastClickStep(processingEnv));
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
BindViewStep中代码如下:
- annotations 方法定义了该step需要处理的注解类型
- process方法为实际的处理逻辑,它包含生成java文件和写java文件两个步骤,生成java文件通过javapoet来操作,这个部分比较复杂,后续篇幅再说明。
public class BindViewStep extends BasicStep {
public BindViewStep(ProcessingEnvironment processingEnv) {
super(processingEnv);
}
@Override
protected void process(List<Element> elementsByAnnotation) {
// 1. build class file
TypeSpec typeSpec = buildClass(elementsByAnnotation);
// 2.write file
//如何解决包名获取问题
JavaFile javaFile = JavaFile.builder("com.hch",typeSpec).build();
mProcessionEnv.getMessager().printMessage(Diagnostic.Kind.NOTE , javaFile.toString());
try {
javaFile.writeTo(mProcessionEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
private TypeSpec buildClass(List<Element> elementsByAnnotation) {
Element enclosingElement = elementsByAnnotation.get(0).getEnclosingElement();
TypeMirror enclosingType = enclosingElement.asType();
TypeSpec typeSpec = TypeSpec.classBuilder(enclosingElement.getSimpleName().toString()+"_viewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(buildMethod(elementsByAnnotation))
.addSuperinterface(IBinder.class)
.build();
return typeSpec;
}
private MethodSpec buildMethod(List<Element> elementsByAnnotation) {
Element enclosingElement = elementsByAnnotation.get(0).getEnclosingElement();
TypeMirror enclosingType = enclosingElement.asType();
MethodSpec.Builder build = null;
build = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(TypeName.VOID)
//从一个element类型获取一个Type
// .addParameter(TypeName.get(enclosingType) , "target");
.addParameter(TypeName.get(Object.class) , "target");
for (Element variable : elementsByAnnotation) {
//如何获取转化类型
build.addStatement("(($L)target).$L = (android.view.View)(($L)target).findViewById($L)"
, enclosingType.toString()
, variable.getSimpleName().toString()
, enclosingType.toString()
, variable.getAnnotation(BindView.class).value());
}
MethodSpec methodSpec = build.build();
return methodSpec;
}
@Override
public Set<String> annotations() {
return new HashSet<String>(Arrays.asList(BindView.class.getCanonicalName()));
}
}
五.app中的依赖
app中只需要简单依赖annotation,并且申明 annotationProcessor即可
implementation project(':annotation')
annotationProcessor project(':annotationProcessor')
选择 build-rebuild project ,会在javac这个任务的任务下输出处理注解的内容 ,例子中的输出如下:
Task :app:compileHuaweiApi21DebugJavaWithJavac
The following annotation processors are not incremental: jetified-annotationProcessor-1.1.0.jar (project :annotationProcessor), jetified-auto-service-1.0-rc3.jar (com.google.auto.service:auto-service:1.0-rc3).
Make sure all annotation processors are incremental to improve your build speed.
注: BindViewStep xxxxxxxx key=MainActivity element=testAnnotation elementKind=FIELD asType=android.view.View element.getEnclosingElement=com.hch.MainActivity element.getEnclosingElement.asType=com.hch.MainActivity element.getEnclosingElement=class com.sun.tools.javac.code.Symbol$ClassSymbol
注:
注: BindViewStep xxxxxxxx key=MainActivity element=testDagger elementKind=FIELD asType=android.view.View element.getEnclosingElement=com.hch.MainActivity element.getEnclosingElement.asType=com.hch.MainActivity element.getEnclosingElement=class com.sun.tools.javac.code.Symbol$ClassSymbol
生成的辅助类内容如下:
package com.hch;
import com.hch.annotation.IBinder;
import java.lang.Object;
import java.lang.Override;
public class MainActivity_viewBinding implements IBinder {
@Override
public void bind(Object target) {
((com.hch.MainActivity)target).testAnnotation = (android.view.View)((com.hch.MainActivity)target).findViewById(2131231093);
((com.hch.MainActivity)target).testDagger = (android.view.View)((com.hch.MainActivity)target).findViewById(2131231094);
}
}
六.注解中的一些基础概念
1.Element
public class Foo { // TypeElement
private int a; // VariableElement
private Foo other; // VariableElement
public Foo() {} // ExecuteableElement
public void setA( // ExecuteableElement
int newA // TypeElement
) {
}
}
2.MirrorType
代表所处理的注解在java语言中的class类型
3.Elements
用来对程序元素(Element)进行操作的实用工具方法
4.Types
用来对类型(MirrorType)进行操作的实用工具方法。
5.ProcessingEnvironment
编写新文件、报告错误消息并查找其他实用工具的聚合上下文环境