文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

注解处理器APT怎么生成

2023-07-05 07:26

关注

今天小编给大家分享一下注解处理器APT怎么生成的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

一、定义

注解处理器(Annotation Processing Tool,简称APT),是JDK提供的工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。处理方式大部分都是根据注解的信息生成新的Java代码与文件。

APT使用相当广泛,EventBus、ARouter、ButterKnife等流行框架都使用了该技术。

二、生成注解处理器

2.1 创建注解模块

在项目中新建Module,选择【Java or Kotlin Library】,名字和包名随意填入,点击Finish。

注解处理器APT怎么生成

② 在模块中定义注解,注解保留范围选择SOURCE即可(因为APT是作用在源码阶段的,生成class之前),当然选择CLASS和RUNTIME也可以,因为他们都包含SOURCE阶段。

我们创建一个Test注解,并且包含int、String、Class、String[]四种类型。

@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)public @interface Test {    int key();    String value() default "";    Class clazz();    String[] array() default {};}

2.2 创建注解处理器模块

① 在项目中新建Module,选择【Java or Kotlin Library】,名字和包名随意填入,点击Finish。

注解处理器APT怎么生成

② 修改新建Module的build.gradle文件,根据是否使用Kotlin分为两种情况

项目不使用Kotlin:

apply plugin: "java-library"dependencies {// 注解模块(必选)    implementation project(':lib-annotation')// 注解处理器(必选)    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'    // 生成代码方式之一(可选)    implementation 'com.squareup:javapoet:1.13.0'}sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8

项目使用Kotlin:

apply plugin: "java-library"apply plugin: "kotlin"apply plugin: "kotlin-kapt"dependencies {// 注解模块(必选)    implementation project(':lib-annotation')    // 注解处理器(必选)    kapt 'com.google.auto.service:auto-service:1.0-rc7'    implementation 'com.google.auto.service:auto-service:1.0-rc7'    // 生成代码方式之一(可选)    implementation 'com.squareup:javapoet:1.13.0'}sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8

2.3 创建注解处理器

在2.2的注解处理器模块中新建类,继承自AbstractProcessor类
使用@AutoService、@SupportedAnnotationTypes、@SupportedSourceVersion注解注释该类,注解处理器即创建完成,具体如下:

// 让该类拥有了获取注解的能力(必选)@AutoService(Processor.class)// 设置该处理器支持哪几种注解(必选)// 字符串类型,例:com.kproduce.annotation.TEST@SupportedAnnotationTypes({Const.CARD_ANNOTATION,Const.TEST_ANNOTATION})// 源码版本(可选)@SupportedSourceVersion(SourceVersion.RELEASE_8)public class TestAnnotationProcessor extends AbstractProcessor {    @Override    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);    }        @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        return false;    }}

2.4 在app模块中引入注解处理器

在主项目app模块的build.gradle中引入注解处理器,使用kapt或annotationProcessor修饰,所以在gradle中看到这两种修饰的项目就是注解处理器项目了。

dependencies {// 注解模块    implementation project(":lib-annotation")    // 注解处理器模块,以下二选一    // 使用Kotlin选择这种    kapt project(":compiler")    // 使用Java选择这种    annotationProcessor project(":compiler")}

2.5 测试

经过上面的一系列操作,注解处理器已经注册完成,在其process方法中会接收到想要处理的注解。

① 在项目中使用@Test注解

@Test(id = 100, desc = "Person类", clazz = Person.class, array = {"111", "aaa", "bbb"})public class Person {}

② 在注解处理器的init方法中,可以通过ProcessingEnvironment参数获取Messager对象(可以打印日志),在process方法中获取到注解后输出日志查看被注解的类名。(注意:如果打印日志使用Diagnostic.Kind.ERROR,会中断构建)

@AutoService(Processor.class)@SupportedAnnotationTypes(Const.TEST_ANNOTATION)@SupportedSourceVersion(SourceVersion.RELEASE_8)public class TestAnnotationProcessor extends AbstractProcessor {    private Messager messager;    @Override    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);        // 获取Messager对象        messager = processingEnv.getMessager();    }    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {    // 获取所有的被Test注解的对象,无论是类还是属性都会被封装成Element        for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) {            messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>GetAnnotation:" + element.getSimpleName());        }        return false;    }}

③ 构建项目,查看日志,成功获取到注解注释过的类名

注解处理器APT怎么生成

三、解析注解

获取到了注解,我们看一下如何正确的拿到注解内的信息,在注解处理器中类、方法、属性都会被形容成Element,由于我们定义的@Test只修饰类,所以Element也都是类。

@AutoService(Processor.class)@SupportedAnnotationTypes(Const.TEST_ANNOTATION)@SupportedSourceVersion(SourceVersion.RELEASE_8)public class TestAnnotationProcessor extends AbstractProcessor {    private Messager messager;    // 这个是处理Element的工具    private Elements elementTool;    @Override    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);        messager = processingEnv.getMessager();        elementTool = processingEnv.getElementUtils();    }    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {    // 拿到被Test修饰的Element,因为我们只修饰类,所以拿到的Element都是类        for (Element element : roundEnv.getElementsAnnotatedWith(Test.class)) {        // ===============获取当前被修饰的类的信息===============                // 获取包名,例:com.kproduce.androidstudy.test            String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();            // 获取类名,例:Person            String className = element.getSimpleName().toString();// 拼装成文件名,例:com.kproduce.androidstudy.test.Person            String fileName = packageName + Const.DOT + className;            // ===============解析注解===============// 获取注解            Test card = element.getAnnotation(Test.class);// 注解中的int值            int id = card.id();            // 注解中的String            String desc = card.desc();            // 注解中的数组[]            String[] array = card.array();            // 获取类有比较奇葩的坑,需要特别注意!// 在注解中拿Class然后调用getName()会抛出MirroredTypeException异常// 处理方式可以通过捕获异常后,在异常中获取类名            String dataClassName;            try {                dataClassName = card.clazz().getName();            } catch (MirroredTypeException e) {                dataClassName = e.getTypeMirror().toString();            }        }        return true;    }}

四、生成代码

获取到了注解信息,下一步就是根据注解生成Java代码了。但是生成代码的意义是什么呢?

答:WMRouter路由在获取到了注解信息之后,会根据注解的内容生成路由注册的代码。 把一些复杂的可能写错的冗长的代码变成了自动生成,避免了人力的浪费和错误的产生。下面是WMRouter生成的代码:

注解处理器APT怎么生成

目前生成Java代码有两种方式,原始方式和JavaPoet。原始方式理解即可,咱们使用JavaPoet来解析注解、生成代码。

4.1 原始方式

原始方式就是通过流一行一行的手写代码。
优点:可读性高。
缺点:复用性差。

咱们看一下EventBus的源码就能更深刻的理解什么是原始方式:

// 截取EventBusAnnotationProcessor.java中的片段private void createInfoIndexFile(String index) {    BufferedWriter writer = null;    try {        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);        int period = index.lastIndexOf('.');        String myPackage = period > 0 ? index.substring(0, period) : null;        String clazz = index.substring(period + 1);        writer = new BufferedWriter(sourceFile.openWriter());        if (myPackage != null) {            writer.write("package " + myPackage + ";\n\n");        }        writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");        writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");        writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");        writer.write("import java.util.HashMap;\n");        writer.write("import java.util.Map;\n\n");        writer.write("\n");        writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");        writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");        writer.write("    static {\n");        writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");        writeIndexLines(writer, myPackage);        writer.write("    }\n\n");        writer.write("    private static void putIndex(SubscriberInfo info) {\n");        writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");        writer.write("    }\n\n");        writer.write("    @Override\n");        writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");        writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");        writer.write("        if (info != null) {\n");        writer.write("            return info;\n");        writer.write("        } else {\n");        writer.write("            return null;\n");        writer.write("        }\n");        writer.write("    }\n");        writer.write("}\n");    } catch (IOException e) {        throw new RuntimeException("Could not write source for " + index, e);    } finally {        if (writer != null) {            try {                writer.close();            } catch (IOException e) {                //Silent            }        }    }}

4.2 JavaPoet

JavaPoet是使用Java的API和面向对象思想来生成.java文件的库。
优点:面向对象思想、复用性高。
缺点:学习成本高、可读性一般。

因为学习点比较多,咱们仅对用到的API进行说明,其他的可以参考GitHub地址,里面有相当全面的教程。

4.2.1 生成代码

我们先用JavaPoet生成一个HelloWorld类,下面是我们想要的Java代码:

package com.example.helloworld;public final class HelloWorld {  public static void main(String[] args) {    System.out.println("Hello, JavaPoet!");  }}

在JavaPoet中使用了面向对象的思想,万物皆对象,方法和类也变成了对象。在类中代码主要被分为了两块,一块是方法(MethodSpec),一块是类(TypeSpec)。

注解处理器APT怎么生成

接下来我们使用JavaPoet生成这段代码,比较易懂。
① 先创建main方法的MethodSpec对象;
② 再创建HelloWorld类的TypeSpec对象,将main方法传入。

// 创建main方法的MethodSpec对象MethodSpec main = MethodSpec.methodBuilder("main")// 方法名:main    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)// 方法修饰:public static    .returns(void.class)// 返回类型 void    .addParameter(String[].class, "args")// 参数:String[] args    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")// 内容System.out.println("Hello, JavaPoet!");    .build();    // 创建HelloWorld类的TypeSpec对象,将main方法传入TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")// 类名:HelloWorld    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)// 类修饰:public final    .addMethod(main)// 添加方法main    .build();// 构建生成文件,第一个参数为包名JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)    .build();javaFile.writeTo(System.out);

经过以上步骤就可以生成HelloWorld类。

4.2.2 JavaPoet中的自定义类型

上面代码中会发现几个奇怪的类型写在字符串中,详细的讲解可以查看GitHub地址。

$L:值,可以放各种对象,比如int,Object等。
$S:字符串。
$T:类的引用,会自动导入该类的包,比如new Date()中的Date。
$N:定义好的Method方法名,可以调用代码中的其他方法。

4.2.3 各种案例

提供几个案例,更好的理解JavaPoet,详细的讲解可以查看GitHub地址。

① 循环

void main() {  int total = 0;  for (int i = 0; i < 10; i++) {    total += i;  }}// JavaPoet方式 1MethodSpec main = MethodSpec.methodBuilder("main")    .addStatement("int total = 0")    .beginControlFlow("for (int i = 0; i < 10; i++)")    .addStatement("total += i")    .endControlFlow()    .build();    // JavaPoet方式 2MethodSpec main = MethodSpec.methodBuilder("main")    .addCode(""        + "int total = 0;\n"        + "for (int i = 0; i < 10; i++) {\n"        + "  total += i;\n"        + "}\n")    .build();

② ArrayList

package com.example.helloworld;import com.mattel.Hoverboard;import java.util.ArrayList;import java.util.List;public final class HelloWorld {  List<Hoverboard> beyond() {    List<Hoverboard> result = new ArrayList<>();    result.add(new Hoverboard());    result.add(new Hoverboard());    result.add(new Hoverboard());    return result;  }}// JavaPoet方式ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");ClassName list = ClassName.get("java.util", "List");ClassName arrayList = ClassName.get("java.util", "ArrayList");TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);MethodSpec beyond = MethodSpec.methodBuilder("beyond")    .returns(listOfHoverboards)    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)    .addStatement("result.add(new $T())", hoverboard)    .addStatement("result.add(new $T())", hoverboard)    .addStatement("result.add(new $T())", hoverboard)    .addStatement("return result")    .build();

③ 属性

public class HelloWorld {  private final String android;  private final String robot;}// JavaPoet方式FieldSpec android = FieldSpec.builder(String.class, "android")    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)    .build();    TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")    .addModifiers(Modifier.PUBLIC)    .addField(android)    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)    .build();

以上就是“注解处理器APT怎么生成”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯