热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java编译期注解处理器APT

1.APT简介1.1什么是APT?APT(AnnotationProcessingTool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具。APT可以用来在编译

Java编译时注解处理器APT


1. APT简介


1.1 什么是APT?

APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具。APT可以用来在编译时扫描和处理注解。



1.2 APT的作用

通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。在Android中有如ButterKnife、Dagger、EventBus等第三方框架,都采用了APT。
注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。



2. 创建APT的项目结构

APT项目结构


2.1 创建Android Module命名为app(主工程)

dependencies {...// 导入自定义注解implementation project(":apt-annotation")// 指定注释处理器annotationProcessor project(":apt-compiler")...
}

2.2 创建Java library Module命名为 apt-annotation 存放自定义注解

gradle配置文件如下:

apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])
}sourceCompatibility = "1.8"
targetCompatibility = "1.8"

2.3 创建Java library Module命名为 apt-compiler 依赖 apt-annotationauto-service

gradle配置文件如下:

apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation project(":apt-annotation")// 注册注解,并对其生成META-INF的配置信息implementation 'com.google.auto.service:auto-service:1.0-rc6'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'// 第三方自动生成代码框架(可以通过类的调用方式来自动生成代码,简洁高效)implementation 'com.squareup:javapoet:1.10.0'
}sourceCompatibility = "1.8"
targetCompatibility = "1.8"

为什么两个模块一定要是Java Library而不是Android Library?



  • 如果创建Android Library模块会发现不能找到AbstractProcessor这个类,这是因为Android平台是基于OpenJDK的,而OpenJDK中不包含APT的相关代码。因此,在使用APT时,必须在Java Library中进行。



3. AbstractProcessor分析(核心)

AbstractProcessor是一个抽象类,需要通过继承它来实现我们自己的注解解释器

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {/*** 注解处理器的初始化阶段,可以通过ProcessingEnvironment来获取一些帮助我们来处理注解的工具类*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);}/*** 指明有哪些注解需要被扫描到,返回注解的全路径(包名+类名)*/@Overridepublic Set<String> getSupportedAnnotationTypes() {return super.getSupportedAnnotationTypes();}/*** 用来指定当前正在使用的Java版本,一般返回SourceVersion.latestSupported()表示最新的java版本即可*/@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}/*** 核心方法,注解的处理和生成代码都是在这个方法中完成*/@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {return false;}
}


  • @AutoService的作用是用来生成META-INF/services/javax.annotation.processing.Processor文件,并且自动将该注解标记的注解处理器添加到该文件中。

  • 在注解处理器的初始化阶段,可以通过参数ProcessingEnvironment来获取一些帮助我们来处理注解的工具类

// Element操作类
Elements elementUtils = processingEnv.getElementUtils();// 类信息工具类
Types typeUtils = processingEnv.getTypeUtils();// 日志工具类
Messager messager = processingEnv.getMessager();// 文件工具类
Filer filer = environment.getFiler();

3.1 了解Element

在Java中Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量。Element已知的子接口有如下几种:



  • PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。

  • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。

  • TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。

  • VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

我们就拿一个简单的类举例,就可以很容易理解这些元素。


不同类型Element其实就是映射了Java中不同的类元素


package com.aptdemo.user; // PackageElementpublic class User { // TypeElementprivate long id; // VariableElementprivate String username; // VariableElementpublic long getId() { // ExecutableElementreturn id;}public void setId( // ExecutableElementlong id) { // VariableElementthis.id = id;}public String getUsername() { // ExecutableElementreturn username;}public void setUsername( // ExecutableElementString username) { // VariableElementthis.username = username;}
}

4. APT实例讲解


仿造ButterKnife中的 @BindView 注解,自动生成findViewById代码



4.1 在apt-annotation模块中自定义注解 @BindView

@Target(ElementType.FIELD) // 作用于成员属性上
@Retention(RetentionPolicy.CLASS)
public @interface BindView {int value();
}

4.2 在apt-compiler模块中,编写相关的注解处理逻辑

解析相关的节点信息存放到NodeInfo

public class NodeInfo {/*** 包路径*/private String packageName;/*** 节点所在类名称*/private String className;/*** 节点类型名称*/private String typeName;/*** 节点名称*/private String nodeName;/*** 注解的value*/private int value;public NodeInfo(String packageName, String className, String typeName,String nodeName, int value) {this.packageName = packageName;this.className = className;this.typeName = typeName;this.nodeName = nodeName;this.value = value;}public String getPackageName() {return packageName;}public String getClassName() {return className;}public String getTypeName() {return typeName;}public String getNodeName() {return nodeName;}public int getValue() {return value;}
}

编写核心的注解处理器

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {/*** Element操作类*/private Elements mElementUtils;/*** 类信息工具类*/private Types mTypeUtils;/*** 日志工具类*/private Messager mMessager;/*** 文件创建工具类*/private Filer mFiler;/*** 节点信息缓存*/private Map<String, List<NodeInfo>> mCache = new HashMap<>();/*** 注解处理器的初始化阶段,可以通过ProcessingEnvironment来获取一些帮助我们来处理注解的工具类*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mElementUtils = processingEnv.getElementUtils();mTypeUtils = processingEnv.getTypeUtils();mMessager = processingEnv.getMessager();mFiler = processingEnv.getFiler();}/*** 指明有哪些注解需要被扫描到,返回注解的全路径(包名+类名)*/@Overridepublic Set<String> getSupportedAnnotationTypes() {return Collections.singleton(BindView.class.getCanonicalName());}/*** 用来指定当前正在使用的Java版本,一般返回SourceVersion.latestSupported()表示最新的java版本即可*/@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}/*** 核心方法,注解的处理和生成代码都是在这个方法中完成*/@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (annotations == null || annotations.isEmpty()) return false;// 获取所有 @BindView 节点Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);if (elements == null || elements.isEmpty()) return false;// 遍历节点for (Element element : elements) {// 获取节点包信息String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();// 获取节点类信息,由于 @BindView 作用于成员属性上,所以这里使用 getEnclosingElement() 获取父节点信息String className = element.getEnclosingElement().getSimpleName().toString();// 获取节点类型String typeName = element.asType().toString();// 获取节点标记的属性名称String nodeName = element.getSimpleName().toString();// 获取注解的值int value = element.getAnnotation(BindView.class).value();// 打印mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName);mMessager.printMessage(Diagnostic.Kind.NOTE, "className:" + className);mMessager.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName);mMessager.printMessage(Diagnostic.Kind.NOTE, "nodeName:" + nodeName);mMessager.printMessage(Diagnostic.Kind.NOTE, "value:" + value);// 缓存KEYString key = packageName + "." + className;// 缓存节点信息List<NodeInfo> nodeInfos = mCache.get(key);if (nodeInfos == null) {nodeInfos = new ArrayList<>();nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));// 缓存mCache.put(key, nodeInfos);} else {nodeInfos.add(new NodeInfo(packageName, className, typeName, nodeName, value));}}// 判断临时缓存是否不为空if (!mCache.isEmpty()) {// 遍历临时缓存文件for (Map.Entry<String, List<NodeInfo>> stringListEntry : mCache.entrySet()) {try {// 创建文件createFile(stringListEntry.getValue());} catch (Exception e) {e.printStackTrace();}}}return false;}private void createFile(List<NodeInfo> infos) throws IOException {NodeInfo info = infos.get(0);String className = info.getClassName() + "$$ViewBinding";// 方法参数ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(info.getPackageName(), info.getClassName()), "target").build();// 代码块CodeBlock.Builder codeBlockBuilder = CodeBlock.builder();for (NodeInfo nodeInfo : infos) {// target.textView = (TextView) target.findViewByID(R.id.text_view);codeBlockBuilder.add("target.$L = ($L)target.findViewById($L);",nodeInfo.getNodeName(),nodeInfo.getTypeName(),nodeInfo.getValue());}// 方法MethodSpec methodSpec = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC, Modifier.STATIC).addParameter(parameterSpec).addStatement(codeBlockBuilder.build()).returns(void.class).build();// 类TypeSpec typeSpec = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC).addMethod(methodSpec).build();// 生成文件JavaFile.builder(info.getPackageName(), typeSpec).build().writeTo(mFiler);}
}

javapoet的具体使用教程请参考:javapoet官方文档



4.3 在app模块中应用注解

在MainActivity中使用注解 @BindView

@BindView(R.id.text_view)
TextView textView;

然后再Rebuild Project,这样就可以在build->generated->ap_generated_sources->debug->包名路径下,看见自动生成的文件MainActivity$$ViewBinding

APT自动生成的文件MainActivity$$ViewBinding代码如下:

public class MainActivity$$ViewBinding {public static void bind(MainActivity target) {target.textView = (android.widget.TextView)target.findViewById(2131165354);;}
}

这样我们就可以仿造ButterKnife,在App模块下新建一个ButterKnife类,利用反射来调用上面方法

public class ButterKnife {public static void bind(Activity target) {try {Class<?> clazz = target.getClass();// 反射获取apt生成的指定类Class<?> bindViewClass = Class.forName(clazz.getName() + "$$ViewBinding");// 获取它的方法Method method = bindViewClass.getMethod("bind", clazz);// 执行方法method.invoke(bindViewClass.newInstance(), target);} catch (Exception e) {e.printStackTrace();}}
}

这里我们的BindView注解功能就大功告成了

public class MainActivity extends AppCompatActivity {@BindView(R.id.text_view)TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);textView.setText("Hello BindView");}
}

5. 参考

注解 - APT编译时注解处理器

Java编译时注解处理器(APT)详解

javapoet官方文档


推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
author-avatar
____洋果子
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有