JSR269--注解处理器在Android中的使用(一)

2022/10/08 note 共 5590 字,约 16 分钟

1. 什么是JSR 269

JSR全程为Java Specification Requests,是java定义规范的一个流程。
JSR 269(https://www.jcp.org/en/jsr/detail?id=269)名称为Pluggable Annotation Processing API,翻译过来就是插入式注解处理API,它是java编译期间的注解处理流程规范,在java 6引入。

  • 在编译期处理注解,获取语法树的任意元素,生成代码,生成资源文件,修改语法树
  • 元编程,method、package、constructor、type、variable、enum、annotation等Java语言元素映射为Types和Elements
  • 它在java工程中的使用方法:https://www.cnblogs.com/fortitude/p/10936386.html, javac -cp mp.jar Sample.java,mp.jar是预先编译的processor的jar包,用它参与目标源码的编译。

  • 在Android中,Android-apt是由一位开发者开发的apt框架,Androi Gradle2.2发布后,提供了annotationProcessor的功能替换了Android-apt的插件功能,本次分享主要集中在annotationProcessor的使用上
  • Kapt,kotlin的注解处理,它基于annotationProcessor,不在这里做介绍了

2. 为什么用JSR 269

  • 通过编译期生成模板代码,减少重复工作,比如getter、setter
  • 减少反射的使用

3. 使用JSR 269的常见库

4. 简单的例子 lib-compiler1

例子源码:https://github.com/itlgl/AnnotationProcessorDemo,后续的示例源码也在里面
示例代码位于lib-compiler1这个module内
通过本示例可以了解到注解处理器的简单用法

4.1 自定义注解

在单独的module lib_annotation中自定义一个注解

package demo.lib_annotation;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Retention(RetentionPolicy.SOURCE)  
@Target(ElementType.TYPE)  
public @interface HelloWorldGen {  
}  

4.2 自定义注解处理器

在单独的module lib-compiler1中定义注解处理器

... 省略导包代码  
  
@AutoService(Processor.class)  
@SupportedSourceVersion(SourceVersion.RELEASE_8)  
//@SupportedAnnotationTypes("demo.lib_annotation.HelloWorldGen")  
public class HelloWorldProcessor extends AbstractProcessor {  
  
    // 可用@SupportedAnnotationTypes("demo.lib_annotation.HelloWorldGen")替代  
    @Override  
    public Set<String> getSupportedAnnotationTypes() {  
        return Collections.singleton(HelloWorldGen.class.getCanonicalName());  
    }  
  
    @Override  
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(HelloWorldGen.class);  
        System.out.println("HelloWorldProcessor -> this=" + this);  
        System.out.println("HelloWorldProcessor -> process elements.size()=" + elements.size());  
        System.out.println("HelloWorldProcessor -> roundEnv.processingOver()=" + roundEnv.processingOver());  
        if(elements.size() > 0) {  
            // HelloWorld.java  
            for (Element element : elements) {  
                try {  
                    String className = "HelloWorld" + element.getSimpleName().toString();  
                    JavaFileObject helloWorld = processingEnv.getFiler()  
                            .createSourceFile("demo." + className, element);  
  
                    Writer writer = helloWorld.openWriter();  
  
                    writer.write("package demo;\n" +  
                            "\n" +  
                            "public final class " + className + " {\n" +  
                            "  public static void main(String[] args) {\n" +  
                            "    System.out.println(\"Hello jsr269!\");\n" +  
                            "  }\n" +  
                            "}");  
                    writer.flush();  
                    writer.close();  
                    System.out.println("HelloWorldProcessor -> gen file:" + className);  
                } catch (IOException e) {  
                    System.out.println("HelloWorldProcessor -> gen file error, " + e);  
                }  
  
                // META-INF/services/test  
                try {  
                    String resourceFile = "META-INF/services/test_" + element.getSimpleName().toString();  
                    FileObject fileObject = processingEnv.getFiler().createResource(  
                            StandardLocation.CLASS_OUTPUT, "", resourceFile, element);  
                    Writer writer = fileObject.openWriter();  
  
                    writer.write("test string");  
  
                    writer.flush();  
                    writer.close();  
                } catch (IOException e) {  
                    System.out.println("HelloWorldProcessor -> create resource error, " + e);  
                }  
            }  
        }  
        return false;  
    }  
}  

从上面的代码可以看到自定义注解处理器的几个要点:

  • 需要继承AbstractProcessor
  • SupportedSourceVersion注解指定java运行时版本
  • 重写getSupportedAnnotationTypes方法,指定需要处理的注解
  • 重写process方法,在里面处理指定注解,生成类文件或资源文件

4.3 使用自定义注解处理器

demo中lib-demo中需要使用刚才定义的注解处理器,在此module的build.gradle中增加配置

annotationProcessor project(':lib-compiler1')  

在类文件上加上自定义注解:

package demo.libdemo;  
  
import demo.lib_annotation.HelloWorldGen;  
  
@HelloWorldGen  
public class Demo1 {  
    int sss = 2;  
}  

4.4 使用效果

build lib-demo后,可以在build目录下,看到processor处理生成的类文件
gen-file-1

4.5 process方法的返回值

当process方法返回true后,在HelloWorldProcessor后执行的其他注解处理器便不能在process方法再处理这个注解了;反之则能拿到并做后续处理

以上面的例子举例,demo的lib-compiler1中有HelloWorldProcessorCopy注解处理器,当HelloWorldProcessor的process方法返回true后,HelloWorldProcessorCopy将不能再次处理注解HelloWorldGen,也就不会生成类文件

5. 执行时机

先看一下java中编译流程的两个图:
Java编译器编译流程
Java编译器编译流程

Javac编译流程图
Javac编译流程图

Java的编译流程基本都是java代码实现的,所以除了javac的入口c函数外,它大部分编译过程实现了自举。
javac编译流程图,JavaCompiler.java
1、解析(parse)与输入到符号表(enter),Scanner.java & Parser.java & Enter.java
2、注解处理,JavacProcessingEnvironment.java
3、分析与代码生成
  ◦ 属性标注与检查(Attr与Check)
  ◦ 数据流分析(Flow)
  ◦ 将泛型类型转换为裸类型(TransType)
  ◦ 解除语法糖(Lower)
  ◦ 生成Class文件(Gen)

从上面的流程可以看出,processor执行在语法解析之后,所以在processor中无法直接访问到项目的类文件,只能拿到注解标注的类的Elements,也无法使用反射处理项目中的类文件。
找到一个有关JVM的分享文件,有兴趣可以对照参考一下:JVM分享20100621.pdf

6. 多个processor执行的生命周期

先看一下两个Processor执行log:

HelloWorldProcessor -> this=demo.lib_compiler2.HelloWorldProcessor@7a70a0b  
HelloWorldProcessor -> process elements.size()=1  
HelloWorldProcessor -> roundEnv.processingOver()=false  
HelloWorldProcessor2 -> this=demo.lib_compiler2.HelloWorldProcessor2@2f32bcb7  
HelloWorldProcessor2 -> process elements.size()=1  
HelloWorldProcessor2 -> roundEnv.processingOver()=false  
HelloWorldProcessor -> this=demo.lib_compiler2.HelloWorldProcessor@7a70a0b  
  
HelloWorldProcessor -> process elements.size()=0  
HelloWorldProcessor -> roundEnv.processingOver()=false  
HelloWorldProcessor2 -> this=demo.lib_compiler2.HelloWorldProcessor2@2f32bcb7  
HelloWorldProcessor2 -> process elements.size()=0  
HelloWorldProcessor2 -> roundEnv.processingOver()=false  
HelloWorldProcessor -> this=demo.lib_compiler2.HelloWorldProcessor@7a70a0b  
  
HelloWorldProcessor -> process elements.size()=0  
HelloWorldProcessor -> roundEnv.processingOver()=true  
HelloWorldProcessor2 -> this=demo.lib_compiler2.HelloWorldProcessor2@2f32bcb7  
HelloWorldProcessor2 -> process elements.size()=0  
HelloWorldProcessor2 -> roundEnv.processingOver()=true  

从上面log可以看出:
1,Processor实例是同一个
2,处理三轮
3,所有processor没有新生成数据后,再进行一轮processOver回调
4,多个processor执行顺序和/META-INF/services/javax.annotation.processing文件内的顺序有关,使用AutoService默认会按照类全名称排序,即HelloWorldProcessor先执行

示例代码下载

文档信息

Search

    Table of Contents