APT 先容
什么是 APT ?
APT 全称 Annotation Processing Tool,翻译过来即注解处置惩罚器。引用官方一段对 APT 的先容:APT 是一种处置惩罚表明的工具, 它对源代码文件举行检测找出此中的注解,并使用注解举行额外的处置惩罚。
APT 有什么用?
APT 能在编译期根据编译阶段注解,给我们主动天生代码,简化使用。很多盛行框架都使用到了APT技能,如 ButterKnife,Retrofit,Arouter,EventBus 等等
APT 工程
1)、APT 工程创建
一样平常情况下,APT 大抵的的一个实现过程:
1、创建一个Java Module ,用来编写注解
2、创建一个Java Module,用来读取注解信息,并根据指定规则,天生相应的类文件
3、创建一个Android Module ,通过反射获取天生的类,举行公道的封装,提供给上层调用
如下图:
这是我的 APT工程,关于 Module名称可以恣意取,按照我上面说的规则去举行就好了
2)、Module 依靠
工程创建好后,我们就必要理清楚各个 Module 之间的一个依靠关系:
1、因为 apt-processor 要读取apt-annotation的注解,以是apt-processor必要依靠apt-annotation
//apt-processor 的 build.gradle 文件dependencies { implementation project(path: ':apt-annotation')}2、app 作为调用层,以上 3 个 Module都必要举行依靠
//app 的 build.gradle 文件dependencies { //... implementation project(path: ':apt-api') implementation project(path: ':apt-annotation') annotationProcessor project(path: ':apt-processor')}APT工程设置好之后,我们就可以对各个 Module 举行一个具体代码的编写了
apt-annotation 注解编写
这个 Module的处置惩罚相对来说很简单,就是编写相应的自界说注解就好了,我编写的如下:
@Inherited@Documented@Retention(RetentionPolicy.SOURCE)@Target({ElementType.TYPE,ElementType.METHOD})public @interface AptAnnotation { String desc() default "";}apt-processor 主动天生代码
这个 Module 相对来说比力复杂,我们把它分为以下 3 个步调:
1、注解处置惩罚器声明
2、注解处置惩罚器注册
3、注解处置惩罚器天生类文件
1)、注解处置惩罚器声明
1、新建一个类,类名按照本身的喜欢取,继承javax.annotation.processing这个包下的 AbstractProcessor类并实现其抽象方法
public class AptAnnotationProcessor extends AbstractProcessor { /** * 编写天生 Java 类的干系逻辑 * * @param set 支持处置惩罚的注解聚集 * @param roundEnvironment 通过该对象查找指定注解下的节点信息 * @return true: 表现注解已处置惩罚,后续注解处置惩罚器无需再处置惩罚它们;false: 表现注解未处置惩罚,大概要求后续注解处置惩罚器处置惩罚 */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; }}重点看下第一个参数中的 TypeElement ,这个就涉及到Element 的知识,我们简单的先容一下:
Element 先容
现实上,Java 源文件是一种布局体语言,源代码的每一个部门都对应了一个特定范例的 Element,比方包,类,字段,方法等等:
package com.dream; // PackageElement:包元素public class Main<T> { // TypeElement:类元素; 此中 <T> 属于 TypeParameterElement 泛型元素 private int x; // VariableElement:变量、罗列、方法参数元素 public Main() { // ExecuteableElement:构造函数、方法元素 }}Java 的 Element是一个接口,源码如下:
public interface Element extends javax.lang.model.AnnotatedConstruct { // 获取元素的范例,现实的对象范例 TypeMirror asType(); // 获取Element的范例,判定是哪种Element ElementKind getKind(); // 获取修饰符,如public static final等关键字 Set<Modifier> getModifiers(); // 获取类名 Name getSimpleName(); // 返回包罗该节点的父节点,与getEnclosedElements()方法相反 Element getEnclosingElement(); // 返回该节点下直接包罗的子节点,比方包节点下包罗的类节点 List<? extends Element> getEnclosedElements(); @Override boolean equals(Object obj); @Override int hashCode(); @Override List<? extends AnnotationMirror> getAnnotationMirrors(); //获取注解 @Override <A extends Annotation> A getAnnotation(Class<A> annotationType); <R, P> R accept(ElementVisitor<R, P> v, P p);}我们可以通过Element获取如上一些信息(写相识释的都是一些常用的)
由 Element衍生出来的扩展类共有 5 种:
1、PackageElement表现一个包程序元素
2、TypeElement表现一个类大概接口程序元素
3、TypeParameterElement 表现一个泛型元素
4、VariableElement表现一个字段、enum常量、方法大概构造方法的参数、局部变量或非常参数
5、ExecutableElement表现某个类大概接口的方法、构造方法或初始化程序(静态大概实例)
可以发现,Element 有时会代表多种元素,比方 TypeElement代表类或接口,此时我们可以通过 element.getKind()来区分:
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);for (Element element : elements) { if (element.getKind() == ElementKind.CLASS) { // 假如元素是类 } else if (element.getKind() == ElementKind.INTERFACE) { // 假如元素是接口 }}ElementKind是一个罗列类,它的取值有很多,如下:
PACKAGE //表现包ENUM //表现罗列CLASS //表现类ANNOTATION_TYPE //表现注解INTERFACE //表现接口ENUM_CONSTANT //表现罗列常量FIELD //表现字段PARAMETER //表现参数LOCAL_VARIABLE //表现本地变量EXCEPTION_PARAMETER //表现非常参数METHOD //表现方法CONSTRUCTOR //表现构造函数OTHER //表现其他关于Element就先容到这,我们接着往下看
2、重写方法解读
除了必须实现的这个抽象方法,我们还可以重写其他 4 个常用的方法,如下:
public class AptAnnotationProcessor extends AbstractProcessor { //... /** * 节点工具类(类、函数、属性都是节点) */ private Elements mElementUtils; /** * 类信息工具类 */ private Types mTypeUtils; /** * 文件天生器 */ private Filer mFiler; /** * 日记信息打印器 */ private Messager mMessager; /** * 做一些初始化的工作 * * @param processingEnvironment 这个参数提供了多少工具类,供编写天生 Java 类时所使用 */ @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); mElementUtils = processingEnv.getElementUtils(); mTypeUtils = processingEnv.getTypeUtils(); mFiler = processingEnv.getFiler(); mMessager = processingEnv.getMessager(); } /** * 吸收外来传入的参数,最常用的情势就是在 build.gradle 脚本文件里的 javaCompileOptions 的设置 * * @return 属性的 Key 聚集 */ @Override public Set<String> getSupportedOptions() { return super.getSupportedOptions(); } /** * 当前注解处置惩罚器支持的注解聚集,假如支持,就会调用 process 方法 * * @return 支持的注解聚集 */ @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } /** * 编译当前注解处置惩罚器的 JDK 版本 * * @return JDK 版本 */ @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); }}注意:getSupportedAnnotationTypes()、getSupportedSourceVersion()和getSupportedOptions()这三个方法,我们还可以接纳注解的方式举行提供:
@SupportedOptions("MODULE_NAME")@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")@SupportedSourceVersion(SourceVersion.RELEASE_8)public class AptAnnotationProcessor extends AbstractProcessor { //...}2)、注解处置惩罚器注册
注解处置惩罚器声明好了,下一步我们就要注册它,此中注册有两种方式:
1、手动注册
2、主动注册
手动注册比力繁琐固定且轻易堕落,不保举使用,这里就不讲了。我们主要看下主动注册
主动注册
1、起首我们要在apt-processor这个 Module 下的 build.gradle 文件导入如下依靠:
implementation 'com.google.auto.service:auto-service:1.0-rc6'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'注意:这两句必须都要加,否则注册不乐成,我之前踩坑了
2、在注解处置惩罚器上加上 @AutoService(Processor.class) 即可完成注册
@AutoService(Processor.class)public class AptAnnotationProcessor extends AbstractProcessor { //...}3)、注解处置惩罚器天生类文件
注册完成之后,我们就可以正式编写天生Java 类文件的代码了,此中天生也有两种方式:
1、通例的写文件方式
2、通过 javapoet 框架来编写
1 的方式比力枯燥,必要把每一个字母都写上,不保举使用,这里就不讲了。我们主要看下通过 javapoet 这个框架天生Java 类文件
javapoet 方式
这种方式更加符合面向对象编码的一个风格,对 javapoet还不熟的朋友,可以去github 上学习一波 传送门,这里我们先容一下它常用的一些类:
TypeSpec:用于天生类、接口、罗列对象的类
MethodSpec:用于天生方法对象的类
ParameterSpec:用于天生参数对象的类
AnnotationSpec:用于天生注解对象的类
FieldSpec:用于设置天生成员变量的类
ClassName:通过包名和类名天生的对象,在JavaPoet中相当于为其指定 Class
ParameterizedTypeName:通过 MainClass和 IncludeClass 天生包罗泛型的Class
JavaFile:控制天生的 Java 文件的输出的类
1、导入 javapoet 框架依靠
implementation 'com.squareup:javapoet:1.13.0'2、按照指定代码模版天生 Java 类文件
比方,我在app的build.gradle下举行了如下设置:
android { //... defaultConfig { //... javaCompileOptions { annotationProcessorOptions { arguments = [MODULE_NAME: project.getName()] } } }}在 MainActivity下面举行了如下注解:
@AptAnnotation(desc = "我是 MainActivity 上面的注解")public class MainActivity extends AppCompatActivity { @AptAnnotation(desc = "我是 onCreate 上面的注解") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAptApi.init(); }}@AptAnnotation(desc = "我是 MainActivity 上面的注解")public class MainActivity extends AppCompatActivity { @AptAnnotation(desc = "我是 onCreate 上面的注解") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAptApi.init(); }}我希望天生的代码如下:
public class HelloWorld { public void test(String param) { System.out.println("模块: apt-app"); System.out.println("节点: MainActivity 描述: 我是 MainActivity 上面的注解"); System.out.println("节点: onCreate 描述: 我是 onCreate 上面的注解"); }}现在我们来实操一下:
@AutoService(Processor.class)@SupportedOptions("MODULE_NAME")@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")@SupportedSourceVersion(SourceVersion.RELEASE_8)public class AptAnnotationProcessor extends AbstractProcessor { //文件天生器 Filer filer; //模块名 private String mModuleName; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //初始化文件天生器 filer = processingEnvironment.getFiler(); //通过 key 获取 build.gradle 中对应的 value mModuleName = processingEnv.getOptions().get("MODULE_NAME"); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set == null || set.isEmpty()) { return false; } //获取当前注解下的节点信息 Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class); // 构建 test 函数 MethodSpec.Builder builder = MethodSpec.methodBuilder("test") .addModifiers(Modifier.PUBLIC) // 指定方法修饰符 .returns(void.class) // 指定返回范例 .addParameter(String.class, "param"); // 添加参数 builder.addStatement("$T.out.println($S)", System.class, "模块: " + mModuleName); if (rootElements != null && !rootElements.isEmpty()) { for (Element element : rootElements) { //当前节点名称 String elementName = element.getSimpleName().toString(); //当前节点下注解的属性 String desc = element.getAnnotation(AptAnnotation.class).desc(); // 构建方法体 builder.addStatement("$T.out.println($S)", System.class, "节点: " + elementName + " " + "描述: " + desc); } } MethodSpec main =builder.build(); // 构建 HelloWorld 类 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC) // 指定类修饰符 .addMethod(main) // 添加方法 .build(); // 指定包路径,构建文件体 JavaFile javaFile = JavaFile.builder("com.dream.aptdemo", helloWorld).build(); try { // 创建文件 javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } return true; }}颠末上面这些步调,我们运行App就能天生上面截图的代码了,现在还差末了一步,对天生的代码举行使用
注意:差别版本的 Gradle 天生的类文件位置大概不一样,我的Gradle 版本是 6.7.1,天生的类文件在如下位置:
一些低版本的 Gradle 天生的类文件在 /build/generated/source这个目次下
apt-api 调用天生代码完成业务功能
这个Module的操作相对来说也比力简单,就是通过反射获取到天生的类,举行相应的封装使用即可,我的编写如下:
public class MyAptApi { @SuppressWarnings("all") public static void init() { try { Class c = Class.forName("com.dream.aptdemo.HelloWorld"); Constructor declaredConstructor = c.getDeclaredConstructor(); Object o = declaredConstructor.newInstance(); Method test = c.getDeclaredMethod("test", String.class); test.invoke(o, ""); } catch (Exception e) { e.printStackTrace(); } }}接着我们在 MainActivity 的 oncreate 方法内里举行调用:
@AptAnnotation(desc = "我是 MainActivity 上面的注解")public class MainActivity extends AppCompatActivity { @AptAnnotation(desc = "我是 onCreate 上面的注解") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyAptApi.init(); }}//打印效果
模块:app
节点: MainActivity 描述: 我是MainActivity上面的注解
节点: onCreate 描述: 我是onCreate 上面的注解
总结
本篇文章讲的一些重点内容:
1、APT 工程所需创建的差别种类的 Module 及Module之间的依靠关系
2、Java源文件现实上是一种布局体语言,源代码的每一个部门都对应了一个特定范例的 Element
3、接纳 auto-service对注解处置惩罚器举行主动注册
4、接纳 javapoet框架编写所需天生的Java类文件
5、通过反射及得当的封装,将天生的类的功能提供给上层调用 |