iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >注解处理器(APT)是什么
  • 800
分享到

注解处理器(APT)是什么

APT注解处理器注解处理器 2023-02-27 14:02:17 800人浏览 安东尼
摘要

目录一、定义二、生成注解处理器2.1 创建注解模块2.2 创建注解处理器模块2.3 创建注解处理器2.4 在app模块中引入注解处理器2.5 测试三、解析注解四、生成代码4.1 原始

上一篇讲完注解,这篇咱们科普一下注解的其中一种用途——注解处理器(APT),文章会手把手的帮助大家学会APT的使用,并使用简单的例子来进行练习。

一、定义

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

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

二、生成注解处理器

2.1 创建注解模块

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

② 在模块中定义注解,注解保留范围选择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。

② 修改新建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_8
targetCompatibility = 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_8
targetCompatibility = 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;
    }
}

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

三、解析注解

获取到了注解,我们看一下如何正确的拿到注解内的信息,在注解处理器中类、方法、属性都会被形容成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生成的代码:

目前生成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)。

接下来我们使用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方式 1
MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();
    
// JavaPoet方式 2
MethodSpec 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。

APT是JDK提供的工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。获取到注解后可以使用原始方法与JavaPoet生成Java代码。

这样APT的介绍就结束了,希望大家读完这篇文章,会对APT有一个更深入的了解。如果我的文章能给大家带来一点点的福利,那在下就足够开心了。

到此这篇关于注解处理器(APT)是什么?的文章就介绍到这了,更多相关APT注解处理器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 注解处理器(APT)是什么

本文链接: https://www.lsjlt.com/news/197692.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 注解处理器(APT)是什么
    目录一、定义二、生成注解处理器2.1 创建注解模块2.2 创建注解处理器模块2.3 创建注解处理器2.4 在app模块中引入注解处理器2.5 测试三、解析注解四、生成代码4.1 原始...
    99+
    2023-02-27
    APT注解处理器 注解处理器
  • 注解处理器APT怎么生成
    今天小编给大家分享一下注解处理器APT怎么生成的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。一、定义注解处理器(Annota...
    99+
    2023-07-05
  • APT 注解处理器实现 Lombok 常用注解功能详解
    目录1 背景2 生成字节码原理2.1 APT(Annotation Processing Tool )注解处理器2.2 AbstractProcessor 注解处理器的使用...
    99+
    2022-11-13
  • spring是怎么处理注解的
    Spring框架是一个基于注解的框架,它可以处理各种注解来实现不同的功能。下面是Spring框架处理注解的一般过程:1. 扫描注解:...
    99+
    2023-08-18
    spring
  • SpringMVC注解之@ResponseBody注解原理是什么
    这篇文章主要介绍SpringMVC注解之@ResponseBody注解原理是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、介绍@ResponseBody 注解的作用是将方法的返回值通过适当的转换器转换为指定的...
    99+
    2023-06-15
  • 怎么使用Android注解处理器
    小编给大家分享一下怎么使用Android注解处理器,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1.定义注解推荐New -> Module -> Ja...
    99+
    2023-06-14
  • 服务器被APT攻击的方式是什么
    这期内容当中小编将会给大家带来有关 服务器被APT攻击的方式是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。服务器被APT 攻击了应该怎么办近两年频发爆出某些企业网站出现长期服务器运营不正常的信息。互...
    99+
    2023-06-07
  • spring注解校验原理是什么
    这篇文章主要介绍“spring注解校验原理是什么”,在日常操作中,相信很多人在spring注解校验原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”spring注解校验原理是什么”的疑惑有所帮助!接下来...
    99+
    2023-06-17
  • java 什么是注解
    概念:说明程序的。给计算机看的。注释:用文字来描述程序,给程序员看的。定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方...
    99+
    2017-12-23
    java教程 java 注解
  • 什么是java注解?
    自Java5.0版本引入注解之后,它就成为了Java平台中非常重要的一部分。开发过程中,我们也时常在应用代码中会看到诸如@Override,@Deprecated这样的注解。那么什么是注解?下面给大家介绍一下。什么是注解?注解也叫元数据,即...
    99+
    2014-10-03
    java教程 java 注解
  • 如何使用Android注解处理器
    我们就可以结合今天的Annotation Processing Tool(APT)来自定义注解处理器。 注解处理器简单解释就是收集我们标记的注解,处理注解上提供的信息。 本篇用我之前...
    99+
    2022-11-12
  • Android AOP之注解处理解释器详解(二)
    Android APO 注解处理解释器 相关文章: Android AOP注解Annotation详解(一) Android AOP之注解处理解释器详解(二) Android ...
    99+
    2022-06-06
    注解 解释器 aop Android
  • Java注解处理器的示例分析
    这篇文章将为大家详细讲解有关Java注解处理器的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就...
    99+
    2023-05-30
  • spring注解的底层实现原理是什么
    Spring注解的底层实现原理主要依赖于Java的反射机制。在Spring中,通过使用注解来标识类、方法或字段,从而告诉Spring...
    99+
    2023-10-09
    spring
  • java中什么是元注解
    本篇文章为大家展示了java中什么是元注解,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用...
    99+
    2023-06-14
  • Spring注解驱动之BeanPostProcessor后置处理器讲解
    目录概述BeanPostProcessor后置处理器概述BeanPostProcessor后置处理器实例BeanPostProcessor后置处理器作用bean的初始化和销毁流程Be...
    99+
    2022-11-13
  • SpringBoot的@Autowired注解注入规则是什么
    这篇文章主要讲解了“SpringBoot的@Autowired注解注入规则是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot的@Autowired注解注入规则是什么”吧...
    99+
    2023-06-25
  • css预处理器是什么东西
    本篇内容主要讲解“css预处理器是什么东西”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“css预处理器是什么东西”吧! css预处理...
    99+
    2022-10-19
  • vue.js中什么是事件处理器
    这期内容当中小编将会给大家带来有关vue.js中什么是事件处理器,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。监听事件可以用v-on指令监听DOM事件来触发一些javas...
    99+
    2022-10-19
  • css预处理器指的是什么
    这篇文章主要介绍css预处理器指的是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!CSS预处理器是一种专门的编程语言,用来为CSS增加一些编程特性(CSS本身不是编程语言)。不需考虑浏览器兼容问题,因为CSS预处...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作