APT(Annotation Processing Tool)
APT是处理Java代码的注解的工具,它对源代码文件进行监测找出其中的Annotation,根据注解从而自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。也可以这样理解,只要通过声明APT工具后,程序在编译期自定义的注解解释器才能执行。
简单点:根据规则,自动生成代码、生成Class类文件
第三方框架
如果用过以下框架,都有用到注解声明。分别在变量、函数、接口类上等....
- xUtils
- @Event(value=R.id.xxx,type=View.OnClickListener.class)
- Butternife
- @BindView(R.id.xxx)
- EventBus
- @Subscribe(threadMode = ThreadMode.MainThread) 事件接受通知
- Dagger2
- @inject @Component @name
- Arouter
- @Route(path = "/app/xxxActivity")
APT结构思路
对于java源文件来说,它同样也是一种结构体语言:
package com.netease.apt; // PackageElement 包元素/节点
public class Main{ // TypeElement 类元素/节点
private int x; // VariableElement 属性元素/节点
private Main(){ // ExecuteableElement 方法元素/节点
}
private void print(String msg){}
}
PackageElement
表示一个包程序元素,提供对有关包及其成员的信息的访问
ExecutableElement
表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
TypeElement
表示一个类或接口程序元,提供对有关类型及其成员的信息的访问
VariableElement
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
API
# getEnclosedElements()
返回该元素直接包含的子元素
# getEnclosingElement()
返回包含该element的父element,与上一个方法相反 getKind() 返回element的类型,判断是哪种element
# getModifiers()
获取修饰关键字,入public static final等关键字
# getSimpleName()
获取名字,不带包名
# getQualifiedName()、
获取全名,如果是类的话,包含完整的包名路径
# getParameters()
获取方法的参数元素,每个元素是一个VariableElement
# getReturnType()
获取方法元素的返回值
# getConstantValue()
如果属性变量被final修饰,则可以使用该方法获取它的值
java代码生成器
传统方案-JavaFileObject
在EventBus中,是通过JavaFileObject
+ BufferedWriter
以行代码块的方式从上到下的顺序进行编写生成
JavaPoet
Square
推出的开源java代码生成框架,提供了java Api生成.java
源文件,这个框架功能非常实用,也是面向对象OOP
思想,可以很优雅的自动化生成代码方式。
不死板灵活性高,函数(实现) -> Class & interface -> 包装类
# MethodSpec
代表一个构造函数或方法声明
# TypeSpec
代表一个类,接口,或者枚举声明
# FieldSpec
代表一个成员变量,一个字段声明
# JavaFile
包含一个顶级类的Java文件
# ParameterSpec
用来创建参数
# AnnotationSpec
用来创建注解
# ClassName
用来包装一个类
# TypeName
类型,如在添加返回值类型是使用 TypeName.VOID
$S 字符串,如:$S, ”hello”
$T 类、接口,如:$T, MainActivity
**假设要生成的 Hello JavaPoet Class
**
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
JavaPoet格式
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
用JavaPoet生成Class
- 创建App
- 新建java-Library,
annotations
定义注解 - 新建java-Library,
annotations-compiler
并且继承AbstractProcessor
注解解析器 - 在函数
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
JavaPoet
结构体
注解(补)
可以将注解看成一种特殊的接口,一种特殊的属性元素,注解本质是接口。
例如 butterknife 的一段注解,@BindView(R2.id.test_tv)
package butterknife;
import androidx.annotation.IdRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(FIELD) //作用属性字段、枚举的常量
@Retention(RUNTIME) //运行期
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
元注解
元注解是在注解中的注解声明,作用在注解之上,方便实现需实现的功能。
@Target、@Retention、@Document、@Inherited、@Repeatable(jdk1.8加入)
Target 表示该注解打在代码的什么范围之上
@Target(ElementType.TYPE) 作用接口、类、枚举、注解
@Target(ElementType.FIELD) 作用属性字段、枚举的常量
@Target(ElementType.METHOD) 作用方法
@Target(ElementType.PARAMETER) 作用方法参数
@Target(ElementType.CONSTRUCTOR) 作用构造函数
@Target(ElementType.LOCAL_VARIABLE) 作用局部变量
@Target(ElementType.ANNOTATION_TYPE) 作用于注解(@Retention注解中就使用该属性)
@Target(ElementType.PACKAGE) 作用于包
@Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8)
@Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8)
Retention 表示当该注解保留的执行周期
注意:并且存在生命周期:SOURCE(编写期) < CLASS(编译期) < RUNTIME(运行期)
- @Retention(RetentionPolicy.SOURCE) 编写期,常用语监测 例如范围,not-null
- @Retention(RetentionPolicy.CLASS), 默认的保留策略,java代码编译成Class时候的,配合JavaPoet使用
- @Retention(RetentionPolicy.RUNTIME), 程序运行时候,注解会在class字节码文件中存在,通常配合反射Hook机制进行获取,对代码的操作AOP
Documented 文档,够将注解中的元素包含到 Javadoc 中去
Inherited 继承,子注解没有打注解可以继承父类的元注解
Repeatable 可重复,可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义
通过注解解析器用JavaPoet生成Class
App -- build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.myjavapoet"
minSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// 注解解析器时候可以接受在传递的参数
javaCompileOptions {
annotationProcessorOptions {
arguments = [key: 'hello App']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//依赖注解定义
implementation project(":annotations")
// 依赖注解处理器
annotationProcessor project(":annotations-compiler")
}
@MyHelloCls 创建 annotations
定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MyHelloCls {
}
注解解析器annotations-compiler
builder.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//Google apt Service
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
//JavaPoet
implementation "com.squareup:javapoet:1.9.0"
//依赖注解定义
implementation project(":annotations")
}
注解处理器:MyProcessor
package com.example.annotationscompiler;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
@AutoService(Processor.class) // 启用服务
@SupportedAnnotationTypes({"com.example.annotations.MyHelloCls"}) // 注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 环境的版本
@SupportedOptions("key") //App-Gradle定义
public class MyProcessor extends AbstractProcessor {
// 操作Element的工具类(类,函数,属性,其实都是Element)
private Elements elementTool;
// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
private Types typeTool;
// Message用来打印 日志相关信息
private Messager messager;
// 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
private Filer filer;
private String value;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementTool = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
value = processingEnv.getOptions().get("key");
// Gradle中答应日志,NOTE输出
messager.printMessage(Diagnostic.Kind.NOTE, "init >>>>>>>>>>>>>>>>>>>>>>" + value);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "process >>>>>>>>>>>>>>>>>>>>>>");
if (annotations.isEmpty()) {
return false;
}
//JavaPoet API OOP编写
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(filer);
messager.printMessage(Diagnostic.Kind.NOTE, "try: -------------- ok");
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "生成Test文件时失败,异常:" + e.getMessage());
}
return true;
}
}
init 函数初始化,获取操作对象
@SupportedOptions("key") //App-Gradle定义 是App-build.gradle 定义的参数,可在解析器做参数判断
Messager messager 为输出日志,Gradle构建时候输出
process 扫描全工程注解需要执行解析的