美文网首页
编译时期生成代码

编译时期生成代码

作者: orgcheng | 来源:发表于2020-03-30 19:30 被阅读0次

Android的一些开源框架,经常利用注解来实现一些功能,这里简单分析下

一、首先是定义注解

在AS中,我们一般是新建Java Library模块,模块名包含annotation关键字,表示在这里定义注解

在build.gradle中添加下面的代码,可以解决java控制台输出中文乱码问题

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

这里给出一个注解的例子

/**
 * 生命周期:SOURCE < CLASS < RUNTIME
 * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
 *
 * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。
 *    注解会在class文件中存在,但是在运行时会被丢弃
 *
 * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
 */
@Target(ElementType.TYPE) // 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface ARouter {
    // 详细路由路径(必填),如:"/app/MainActivity"
    String path();
    // 路由组名(选填,如果开发者不填写,可以从path中截取出来)
    String group() default "";
}

当然,此模块中也可以定义一些其他Class类,供注解处理器使用,例如定义RouterBean对象,用来存储注解标记的类信息、path信息和group信息。

二、注解处理器

在AS中仍然需要新建Java Library模块,并在build.gradle文件中添加下面依赖

    // 这个不清楚是不是必须的,如果出错就打开
    // compileOnly'com.google.auto.service:auto-service:1.0-rc4'   
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

    // 帮助我们通过类调用的形式来生成Java代码
    implementation "com.squareup:javapoet:1.9.0"

    // 引入annotation,处理@ARouter注解
    implementation project(':arouter_annotation')

这里必须是Java Library模块,因为使用到 javax包下面的类

import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

接下来就是编写注解处理器Processor了,下面给出一个样例:

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理, 也可以通过getSupportedAnnotationTypes方法返回
@SupportedAnnotationTypes({"com.xiangxue.arouter_annotation.ARouter"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions({"moduleName", "packageNameForAPT"})

public class ARouterProcessor extends AbstractProcessor {

    // 操作Element的工具类(类,函数,属性,其实都是Element)
    private Elements elementTool;
    // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
    private Types typeTool;
    // Message用来打印 日志相关信息
    private Messager messager;
    // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Filer filer;

    private String moduleName;  // (模块传递过来的)模块名  app,personal
    private String packageNameForAPT; // (模块传递过来的) 包名

    // 允许/支持的注解类型,让注解处理器处理, 也可以通过注解SupportedAnnotationTypes指定
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotataions = new LinkedHashSet<String>();
        annotataions.add(ARouter.class.getCanonicalName());
        return annotataions;
    }
    
    // 做初始化工作
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        elementTool = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeTool = processingEnvironment.getTypeUtils();

        moduleName = processingEnvironment.getOptions().get("moduleName");
        packageNameForAPT = processingEnvironment.getOptions().get("packageNameForAPT");
        
        if (options != null && aptPackage != null) {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境搭建完成....");
        } else {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境有问题,请检查传递的options参数...");
        }
    }
    
    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        
        return false;
    }

三、使用注解和注解处理器

在Android的module中,要使用注解和注解处理器,需要在build.gradle中添加下面的依赖和配置选项参数值:

    android{
        defaultConfig{
            // 在gradle文件中配置选项参数值(用于APT传参接收)
            // 同学们注意:切记:必须写在defaultConfig节点下
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName(), packageNameForAPT: "packageNameForAPT"]
                }
            }
        }
    }    



    // 注解模块
    implementation project(":arouter_annotation")

    // 注解处理器
    annotationProcessor project(':arouter_compiler')

在编译时期,会扫码注解,运行注解处理器,执行相关操作,例如生成路由表等。

四、注解处理器生成Java文件

这里可以直接一行一行以字符串的形式,拼接出java代码,然后写到文件,也可以采用javapoet库,以面向对象的形式来实现。

eventbus选择了第一种方式,可以参考了解下,不过javapoet是趋势。具体javapoet可参考https://github.com/square/javapoet

为了快速观察和试验javapoet的效果,可以在注解处理器module中编写测试代码,把生成的结果输出到终端:

public class Test {
    public static void main(String[] args){
        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(System.out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意,这里是Java Library模块,引用了javax 和 javapoet

相关文章

  • 编译时期生成代码

    Android的一些开源框架,经常利用注解来实现一些功能,这里简单分析下 一、首先是定义注解 在AS中,我们一般是...

  • 那些年,陪我们一起加班的Intellij IDEA插件

    lombok插件 带来的好处:使代码更加简洁,通过注解在编译时期生成一些代码(如getter,setter,toS...

  • 使用wasm2c反编译wasm代码

    下载wabt代码 编译wabt代码 反编译wasm代码 以反编译eosio.token.wasm为例: 会生成 e...

  • Arouter原理分析

    ARouter工作原理 在代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activit...

  • 第一章绪论

    编译过程和编译程序结构 五个阶段: 词法分析 语法分析 语义分析和中间代码生成 优化 目标代码生成 编译程序的开发...

  • 编译器前端和后端

    编译器粗略分为词法分析,语法分析,类型检查,中间代码生成,代码优化,目标代码生成,目标代码优化。把中间代码生成及之...

  • gRPC java的编译

    gRPC-java代码生成器 gRPC-java的代码生成器编译需要先编译protobuf,否则会报c++源文件无...

  • 知识点OC文件生成C++文件

    生成 使用clang编译器将Objective-C代码编译成C语言代码, 并生成在一个.cpp的 C++文件中: ...

  • TypeScript Downleveling - 什么是 Ty

    下列 TypeScript 代码: 使用 tsc 编译器编译之后生成的 JavaScript 代码: 为什么字符串...

  • c++:模板

    编译器会根据代码自动生成函数。

网友评论

      本文标题:编译时期生成代码

      本文链接:https://www.haomeiwen.com/subject/uvcbuhtx.html