【JavaPoet】浅析JavaPoet的APT代码生成功能

三、JavaPoet

目录

  • 1、APT技术
    • 1.1 APT是什么?
    • 1.2 APT中的结构体思路
    • 1.3 TypeMirror,element、Class的区别与联系
    • 1.4 APT中常用Api
  • 2、JavaPoet是什么?
  • 3、JavaPoet相关Api说明
  • 4、使用JavaPoet实现代码生成功能
    • 4.1 创建一个注解功能库arouter-annottation
    • 4.2 在app工程下使用注解功能库
    • 4.3 实现注解处理器功能
  • 5、使用JavaPoet生成复杂语句
    • 5.1 Map
    • 5.1 生成返回值Map>
    • 5.2 生成语句Map> groupMap = new HashMap<>();

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的区别与联系

  1. TypeMirror(类型镜像)
    • 概念TypeMirror 是 Java Annotation Processing API 中的一个接口,用于表示编译期间的类型信息。
    • 作用:它用于在注解处理器中获取关于源代码中类型的信息,例如类、接口、数组、原始类型等。
  2. Element(元素)
    • 概念Element 也是 Java Annotation Processing API 中的一个接口,用于表示程序元素,例如包、类、方法、字段等。
    • 作用Element 代表源代码中的一个程序元素,而 TypeMirror 则可以获取该元素的类型信息。在注解处理器中,Element 可以是类、方法等,而通过 Element 可以获取到与之关联的 TypeMirror
  3. 类(Class)
    • 概念:在 Java 编程语言中,类是面向对象编程的基本构造单元,用于封装数据和行为。
    • 角色:类定义了对象的结构和行为,它可以实例化为对象,通过类可以访问其属性和方法。

联系与区别:

  • TypeMirrorElement 都属于 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

【JavaPoet】浅析JavaPoet的APT代码生成功能_第1张图片

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

【JavaPoet】浅析JavaPoet的APT代码生成功能_第2张图片

然后我们在这个包下创建一个注解类

/**
 * 项目名: 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的事件

【JavaPoet】浅析JavaPoet的APT代码生成功能_第3张图片

创建完这个处理器模块以后,我们需要让他可以变成一个具有处理器功能的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注解

【JavaPoet】浅析JavaPoet的APT代码生成功能_第4张图片

然后在for循环里面通过elementToolelement获取了包的信息和类的信息,并且打印了使用使用@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);

【JavaPoet】浅析JavaPoet的APT代码生成功能_第5张图片

然后我们假定了一个生成类型的模板,仿照这个模板为每个被注解的类,自动生成相关类。

/*模板
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文件,点进这个类我们发现和刚才使用的模板一样,只有类和包的信息不一样

【JavaPoet】浅析JavaPoet的APT代码生成功能_第6张图片

【JavaPoet】浅析JavaPoet的APT代码生成功能_第7张图片

5、使用JavaPoet生成复杂语句

5.1 Map

任何的class类型,必须包装

// Map
TypeName methodReturn = ParameterizedTypeName.get(
          ClassName.get(Map.class),         // Map
          ClassName.get(String.class),      // Map
          ClassName.get(RouterBean.class)   // Map
);

5.1 生成返回值Map>

// 返回值 这一段 Map>
TypeName methodReturns = ParameterizedTypeName.get(
        ClassName.get(Map.class),        // Map
        ClassName.get(String.class),    // Map

        // Class> 难度
        ParameterizedTypeName.get(ClassName.get(Class.class),
                // ? extends ARouterPath
                WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
                // WildcardTypeName.supertypeOf() 尝试生成 ? super

        // 最终的:Map>
);

将Class>重新嵌套一层,这里用到一个不常用的api生成? extent

5.2 生成语句Map> groupMap = new HashMap<>();

// Map> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T,$T> $N = new $T<>();",
        ClassName.get(Map.class),
        ClassName.get(String.class),

        // Class 难度
        ParameterizedTypeName.get(ClassName.get(Class.class),
                WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
                ProcessorConfig.GROUP_VAR1,
                ClassName.get(HashMap.class));

你可能感兴趣的:(第三方开源框架,java,JavaPoet,Android,ARouter)