三、JavaPoet
目录
1、APT技术
1.1 APT是什么?
APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。
如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理
主要作用在于:编译的时候 —> 处理注解
1.2 APT中的结构体思路
对于java源文件来说,它同样也是一种结构体语言
package com.netease.apt; // PackageElement 包元素/节点
public class Main { // TypeElement 类元素/节点
private int x; // VariableElement 属性元素/节点
private max() {} // ExecuteableElement 方法元素/节点
private void print(String msg) {}
}
- PackageElement : 表示一个包程序元素。提供对有关包及其成员的信息的访问
- TypeElement :表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
- VariableElement : 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
- ExecutableElement :表示某个类或接口的方法、构造方法或初始化程序(静态或实例
1.3 TypeMirror,element、Class的区别与联系
- TypeMirror(类型镜像):
- 概念:
TypeMirror
是 Java Annotation Processing API 中的一个接口,用于表示编译期间的类型信息。 - 作用:它用于在注解处理器中获取关于源代码中类型的信息,例如类、接口、数组、原始类型等。
- 概念:
- Element(元素):
- 概念:
Element
也是 Java Annotation Processing API 中的一个接口,用于表示程序元素,例如包、类、方法、字段等。 - 作用:
Element
代表源代码中的一个程序元素,而TypeMirror
则可以获取该元素的类型信息。在注解处理器中,Element
可以是类、方法等,而通过Element
可以获取到与之关联的TypeMirror
。
- 概念:
- 类(Class):
- 概念:在 Java 编程语言中,类是面向对象编程的基本构造单元,用于封装数据和行为。
- 角色:类定义了对象的结构和行为,它可以实例化为对象,通过类可以访问其属性和方法。
联系与区别:
TypeMirror
和Element
都属于 Java Annotation Processing API 的一部分,用于编译期间的静态分析和元素处理。TypeMirror
表示类型信息,而Element
表示程序元素。- 在注解处理器中,可以通过
Element
获取与之关联的TypeMirror
,从而获得关于类型的信息。 - 类是一种编程语言中的基本概念,用于组织代码结构。在运行时,类可以实例化为对象,而对象则具有特定的状态和行为。在编译期间,
TypeMirror
表示类的类型信息,而在源代码中,Element
表示类的声明。
1.4 APT中常用Api
方法 | 描述 |
---|---|
getEnclosingElement() | 返回element直接包含的子元素 |
getEnclosedElements() | 获取包含该element的父element,与上面相反 |
getKind() | 返回element的类型,判断是哪种element (类、方法等) |
getModifiers() | 获取element的修饰关键字,如public、static、final等关键字 |
getSimpleName() | 获取element的名字,不带包名 |
getQualifiedName() | 获取element的全名,如果是类的话,包含完整的包名路径 |
getParameters() | 获取element的参数,每个参数是一个VariableElement |
getReturnType() | 获取方法元素的返回值 |
getConstantValue() | 如果属性变量被final修饰,则可以使用该方法获取它的值 |
具体请参考:
https://itmyhome.com/java-api/javax/lang/model/element/Element.html
2、JavaPoet是什么?
JavaPoet的特点是引用oop思想方式进行代码生成,项目地址:JavaPoet
3、JavaPoet相关Api说明
类或标识符 | 描述 |
---|---|
MethodSpec | 代表一个构造函数或方法的声明。 |
TypeSpec | 代表一个类,接口或枚举的声明。 |
FieldSpec | 代表一个成员变量,一个字段声明。 |
JavaFile | 包含一个顶级类的Java文件。 |
ParameterSpec | 用于创建参数。 |
AnnotationSpec | 用于创建注解。 |
TypeName | 类型,例如在添加返回值类型时使用 TypeName.VOID |
ClassName | 用来创建注解,包装一个类,创建参数。 |
$S | 字符串,例如:$S , "hello" |
$T | 类、接口,例如:$T , MainActivity |
$L | 枚举类等特殊类 |
$N | 用于表示一个变量,例如方法名、变量名等标识符 |
$S和$N的区别:主要的区别在于 $N
插入的是一个变量可以被引用,而 $S
插入的是一个字符串的值。
4、使用JavaPoet实现代码生成功能
4.1 创建一个注解功能库arouter-annottation
首先创建一个注解功能库arouter-annottation
然后我们在这个包下创建一个注解类
/**
* 项目名: Modular_JavaPoet
* 文件名: ARouter
* 创建者: lukecc0
* 创建时间:2023/11/29 下午6:21
* 描述: Arouter注解
*/
@Target(ElementType.TYPE) //表示该注解可以被用于类、接口、枚举等类型的声明。
@Retention(RetentionPolicy.CLASS) //在编译期打包,表示该注解将被保留在编译期,而不会被加载到运行时环境。
public @interface ARouter {
String path(); //使用注解的类的类名
String group() default ""; //在使用注解时,可以选择是否提供 group 的值,如果不提供,将使用默认值。
}
4.2 在app工程下使用注解功能库
此时我们在这个app项目中尝试使用@ARouter注解,千万别忘了在build.gradle中导入这个注解依赖。导入的方法还和以前一样
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.modular_javapoet'
compileSdk 33
defaultConfig {
applicationId "com.example.modular_javapoet"
minSdk 32
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
//传递数据到java工程
javaCompileOptions{
annotationProcessorOptions{
arguments = [Student:'hello']
}
}
}
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.6.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation project(":arouter-annottation")
//注解处理器
annotationProcessor project(":compiler")
}
在dependencies下使用依赖
implementation project(":arouter-annottation")
📌注意我们在这里传递了一个数据到java工程,此时先不管它的作用后面会介绍,这个先留意一下就行了。
此时我们在MainActivity中使用@ARouter时发现缺少一个参数path,这个参数就是这个MainActivity类的类名。
package com.example.modular_javapoet;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.example.arouter_annottation.ARouter;
@ARouter(path = "MainActivity")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
此时就可以使用注解了,现在我们给MainActivity加上了注解,那么这个注解的作用是什么呢?我们似乎还没有写
4.3 实现注解处理器功能
新建一个处理器compiler,用于处理@ARouter的事件
创建完这个处理器模块以后,我们需要让他可以变成一个具有处理器功能的java包,我们可以使用auto-service实现这个功能,此时我们再使用JavaPoet完成代码生成的功能。
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
dependencies {
//变成注解处理器功能,背后的服务
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
//帮助我们通过类的方式生成Java代码
implementation 'com.squareup:javapoet:1.13.0'
//依赖注解
implementation project(":arouter-annottation")
}
导入相关依赖以后我们就可以创建一个ARouterProcessor类用于处理@ARouter的事件了,这个类需要继承AbstractProcessor
/**
* 项目名: Modular_JavaPoet
* 文件名: ARouterProcessor
* 创建者: lukecc0
* 创建时间:2023/11/29 下午8:18
* 描述: 使用注解Arouter的处理方法
*/
@AutoService(Processor.class) //启用服务
@SupportedAnnotationTypes({"com.example.arouter_annottation.ARouter"}) //导入注解信息
@SupportedSourceVersion(SourceVersion.RELEASE_7) //设置环境
//接受Android工程的参数
@SupportedOptions("Student")
public class ARouterProcessor extends AbstractProcessor {
//操作Element的工具,类、函数、属性都是Element
private Elements elementTool;
//type(类信息)的工具,操作包含TypeMirror的工具方法
private Types typeTool;
//打印日志
private Messager messager;
//文件生成器,类、资源等就是最后需要生成的文件
private Filer filer;
//初始化
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementTool = processingEnv.getElementUtils();
typeTool = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
String value = processingEnv.getOptions().get("Student");
//打印日志
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>>>>>>>" + value);
}
//在编译时工作,如果没有在其他地方使用注解则该函数无法工作
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>Run.......");
//这行代码的作用是获取所有被 ARouter 注解标注的元素,并将它们存储在一个泛型集合中
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (elements.isEmpty()) {
return false; //无法干活
}
for (Element element : elements) {
//获取包信息
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
//获取简单的类名
String Classname = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被@ARouter注解的类有: " + Classname);
//目标:要生成的文件名称
String finalClassName = Classname + "$$$$$$$ARouter";
ARouter aRouter = element.getAnnotation(ARouter.class);
/*模板
public class MainActivity$$$$$$$ARouter{
public static Class findTargetClass(String path){
return path.equals("MainActivity3") ? MainActivity3.Class : null;
}
}
*/
//1.方法
MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
//方法里面的内容:return path.equals("MainActivity3") ? MainActivity3.Class : null;
// .addStatement("return path.equals($S) ? $T.Class : null", aRouter.path(), element)
.addStatement("return path.equals($S) ? $T.class : null",aRouter.path(), ClassName.get((TypeElement) element))
.build();
//2.类
TypeSpec typeSpecClass = TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(findTargetClass)
.build();
//3、包
JavaFile packagef = JavaFile.builder(packageName, typeSpecClass).build();
//开始生成代码文件
try {
packagef.writeTo(filer);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.NOTE, "代码生成失败: " + finalClassName);
throw new RuntimeException(e);
}
}
return false;
}
}
首先我们先看四个注解参数:
- @AutoService(Processor.class) //启用服务
- @SupportedAnnotationTypes({“com.example.arouter_annottation.ARouter”}) //导入注解信息
- @SupportedSourceVersion(SourceVersion.RELEASE_7) //设置环境
- @SupportedOptions(“Student”) //接受Android工程的参数
前三个注解是用于启动和关联注解信息,最后一个注解就是用于获取刚才我们在app工程里面传递的参数,这个参数有什么作用呢?
在这里我们随便传入了一个的信息,如果在正式开发中我们就可以使用这个app的build.gradle传递我们需要和这个app工程有关的所有信息。
接下来我们在重写了init方法并在里面初始化了基础数据,我们还重写了process方法 ,这个方法的作用就是用于处理@ARouter注解。我们一点点解释
//这行代码的作用是获取所有被 ARouter 注解标注的元素,并将它们存储在一个泛型集合中
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (elements.isEmpty()) {
return false; //无法干活
}
首先我们获取了所有使用过这个注解的类的set集合,然后我们判断了set集合是否为空。
如果不为空,我们需要遍历这个set,获取里面的每个elements,然后就可以操作这些类。
我们刚刚只创建了一个MainActivity类,接下来我们在app工程里面在创建几个类,然后在类前面使用@ARouter注解
然后在for循环里面通过elementTool和element获取了包的信息和类的信息,并且打印了使用使用@ARouter注解的类名。然后修改了使用javapoet自动生成类的类名,又获取了每个类对应的注解器的信息。
//获取包信息
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
//获取简单的类名
String Classname = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "被@ARouter注解的类有: " + Classname);
//目标:要生成的文件名称
String finalClassName = Classname + "$$$$$$$ARouter";
ARouter aRouter = element.getAnnotation(ARouter.class);
然后我们假定了一个生成类型的模板,仿照这个模板为每个被注解的类,自动生成相关类。
/*模板
public class MainActivity$$$$$$$ARouter{
public static Class findTargetClass(String path){
return path.equals("MainActivity3") ? MainActivity3.Class : null;
}
}
*/
//1.方法
MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
//方法里面的内容:return path.equals("MainActivity3") ? MainActivity3.Class : null;
// .addStatement("return path.equals($S) ? $T.Class : null", aRouter.path(), element)
.addStatement("return path.equals($S) ? $T.class : null",aRouter.path(), ClassName.get((TypeElement) element))
.build();
//2.类
TypeSpec typeSpecClass = TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(findTargetClass)
.build();
//3、包
JavaFile packagef = JavaFile.builder(packageName, typeSpecClass).build();
//开始生成代码文件
try {
packagef.writeTo(filer);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.NOTE, "代码生成失败: " + finalClassName);
throw new RuntimeException(e);
}
📌这里就体现了JavaPoet的OOP思想,先生成了方法,在生成类,然后生成包。最后打包生成java文件。
此时运行代码,我们可以做app工程下看见刚刚生成的三个新java文件,点进这个类我们发现和刚才使用的模板一样,只有类和包的信息不一样
5、使用JavaPoet生成复杂语句
5.1 Map<String, RouterBean>
任何的class类型,必须包装
// Map<String, RouterBean>
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
ClassName.get(RouterBean.class) // Map<String, RouterBean>
);
5.1 生成返回值Map<String, Class<? extends ARouterPath>>
// 返回值 这一段 Map<String, Class<? extends ARouterPath>>
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
// Class<? extends ARouterPath>> 难度
ParameterizedTypeName.get(ClassName.get(Class.class),
// ? extends ARouterPath
WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
// WildcardTypeName.supertypeOf() 尝试生成 ? super
// 最终的:Map<String, Class<? extends ARouterPath>>
);
将Class<? extends ARouterPath>>重新嵌套一层,这里用到一个不常用的api生成? extent
5.2 生成语句Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
// Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T,$T> $N = new $T<>();",
ClassName.get(Map.class),
ClassName.get(String.class),
// Class<? extends ARouterPath> 难度
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
ProcessorConfig.GROUP_VAR1,
ClassName.get(HashMap.class));