Java SPI 二 之 Java APT原理及APT实战 - 一步步教你写ButterKnife

news2025/4/6 13:30:13

一、定义

Java APT 是 Java 技术设计的一个 APT 架构,

APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。

APT可以用来在编译时扫描和处理注解, 它可以用来获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。 在Android中有如ButterKnife、Dagger、EventBus等第三方框架,都采用了APT。

常用的使用方式是这样的:

APT + 注解处理器(AbstractProcess)+ 代码处理(javaPoet)+ 处理器注册(AutoService)

这里我们了解下相关概念:

APT

是一个工具,可以用于检查源代码中的注解,并生成相应的代码。

AbstractProcess:

是 Java 中的一个抽象类,用于定义一个处理程序或者一个抽象的执行环境。在 Java 的编程过程中,经常需要在类的开头或者方法中加入注解,来标记这个方法或者类是抽象的,或者是实现了某个接口。这时候,AbstractProcess 类就可以用来定义注解处理器,用来处理这些注解。

APT框架中的核心类是AbstractProcessor,它定义了如何在代码中处理注解。当APT工具扫描文件时,它会调用实现AbstractProcessor类的一个或多个类的process()方法,以捕获和处理注解。

二、APT工作原理

Java Annotations Processing Tool(APT)是一个预处理器,可以在Java代码编译期间读取注解,并生成相关的代码。

它的工作原理如下:

  1. Java编译器会将源文件传递给APT进行处理;
  2. APT会扫描源文件中所有的注解,并找到对应的处理器;
  3. 处理器会对注解进行处理,并生成新的Java代码文件(或其他文件);
  4. 生成的Java代码文件被编译成字节码文件;
  5. 编译器将生成的字节码文件和原始Java代码文件一起打包成jar包或class文件。

APT通过Java标准类库中的javax.annotation.processing包提供注解处理的框架。注解处理器必须实现该包中的特定接口,这些接口定义了APT框架的核心功能。通过实现接口,注解处理器能够直接访问来自编译器的数据,以及用于注解处理的元数据信息。注解处理器总是运行在 Java 编译环境中。

APT的使用可以帮助简化一些重复、冗杂的代码生成工作。

三、APT实战1(运行时注解) - 一步步教你写ButterKnife

使用运行时注解的方式实战简单APT,这个比较简单,就是用反射的方式来实现,

void injectLayout(Context context) {
        // 1. 获取当前class
        Class<?> clazz = context.getClass();
        // 2. 根据class获取class上面的注解
        InjectContenttLayout annotation = clazz.getAnnotation(InjectContenttLayout.class);

        // 3. 获取注解中布局文件的id的值
        int layoutId = annotation.value();
        try {
            // 4. 获取activity中的setContentView方法
            Method method = clazz.getMethod("setContentView", int.class);
            // 5. 执行setContentView方法,传入layoutId参数
            method.invoke(context, layoutId);
        } catch (Exception e) {
        }
    }

四、APT实战2(编译时注解) - 一步步教你写ButterKnife

使用编译时注解的方式实战简单APT,手写ButterKnife框架,我们来写一下布局文件view的注入,比如我们不想写烦人的findViewById方法,直接用个注解来搞定,

本 Demo 下载

其核心思想是java的ioc(inversion of control),也叫di(dependency injection,依赖注入),是一种面向对象编程中的设计模式。下面我们开始

4.1 创建一个项目,如下图

4.2 新建自定义注解

创建一个Java Library Module名称叫 apt-annotation

在这个module中创建自定义注解 @BindView,如下图

4.3 实现APT Compiler处理注解

创建一个Java Library Module名称叫 apt-compiler-processor,并添加注解module依赖

dependencies {
    implementation project(':apt-annotation')
}

这个module的作用主要是用来处理注解,并生成java帮助类文件,拆解步骤为

1、扫描所有被注解标记的Element,获得注解标记的element

2、遍历Element,并根据不同的页面进行分类

3、按规则进行字符串拼接,用于拼接生成帮助类代码

4、使用JavaFileObject写入文件生成java代码

(输出的文件在build->generated-ap_generated_sources->debug->out->包名目录下)

如下图所示

该module在处理注解时,必须继承AbstractProcessor抽象类,入口类为process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)


    /**
     * 注解处理方法,
     *
     * @param set      注解的类型集合
     * @param roundEnv 运行环境
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        System.out.println("start process");
        if (set != null && set.size() != 0) {
            //1、 扫描所有被注解标记的Element,获得被BindView注解标记的element
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);

            categories(elements);

            for (TypeElement typeElement : mToBindMap.keySet()) {
                // 获取帮助类所有代码
                String code = generateCode(typeElement);
                // 构建要生成的帮助类的类名
                String helperClassName = typeElement.getQualifiedName() + "_ButterKnifeTest"; //

                System.out.println("start process 帮助类的类名= " + helperClassName);

                // 输出帮助类的java文件,
                // 在本例中就是MainActivity_ButterKnifeTest.java文件
                // 输出的文件在build->generated-ap_generated_sources->debug->out->包名目录下
                try {
                    System.out.println("生成帮助类 ");
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            return true;
        }
        return false;
    }

当代码写完后,就需要注册APT(如上图所示)

注册一个APT需要以下步骤:

  1. 在main 目录下新建 resources 资源文件夹;
  2. 在 resources文件夹下建立 META-INF/services 目录文件夹;
  3. 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
  4. 在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;

4.4 以上步骤全部完成后, 还需要对外提供API(当然也可以不拆分)

创建一个Android Library Module,名称叫apt-api,并添加依赖

dependencies {
    ...
 
    api project(':apt-annotation')
}

 实现很简单,就是通过反射去调用APT生成的帮助类的方法去实现View的自动绑定,部分代码如下:


    public void inject(Object target) {
        String className = target.getClass().getCanonicalName();
        String helperName = className + "_ButterKnifeTest";
        System.out.println("ButterKnifeTest inject" + helperName);
        try {
            IBindHelper helper = (IBindHelper) (Class.forName(helperName).getConstructor().newInstance());
            helper.inject(target);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

最后就是使用了,在app module里添加依赖

dependencies {
    ...
    annotationProcessor project(':apt-compiler-processor')
    implementation project(':apt-api')
}

4.5 使用如下:

public class MainActivity extends AppCompatActivity {

    @BindView(value = R.id.test_textview)
    public TextView testTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnifeTest.getInstance().inject(this);
        testTextView.setText("手写 butterknife demo");
    }
}

运行代码截图

五、痛点及优化

我们可以看到,按官方文档一步步来写apt比较繁琐,

  • 生成代码时需要字符串拼接,代码量多的时候容易出错,排查困难
  • 需要继承AbstrctProcessor并重写多个方法,写入注解容易遗漏
  • 注册APT的步骤繁琐,需手动创建文件

针对以上问题:

1、我们可以使用JavaPoet来替代拼接字符串( JavaPoet是一个用来生成Java代码的框架,对JavaPoet不了解的请自行学习)

官网地址: GitHub - square/javapoet: A Java API for generating .java source files.

2、使用Auto-Service来自动注册APT

这是谷歌官方出品的一个开源库,可以省去注册APT的步骤,只需要一行注释
先在apt-compiler模块中添加依赖

dependencies {
    ...
    
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
}

然后添加注释即可,如下图所示: 

六、一些疑问

5.1 手写注解处理器时,注解处理器processor为什么要在META-INFO注册?

META-INFO相当于一个信息包,用于存放一些meta information相关的信息,用来配置应用程序、扩展程序、类加载器和服务manifest.mf文件,在编译时,java编译器回去该文件中查找实现了AbstractProcess的子类,就相当于注册。

5.2  APT(Annotation Processing Tool)如何调用AbstractProcess的呢?(注解处理器是如何被系统调用的?)

annotationProcessor 指定apt处理器。

1、创建一个类并继承自 AbstractProcessor

2、通过注解 @AutoService(Processor.class) 将创建的新类注册为 APT 处理器。

也可以手动创建,详细见下面demo说明

3、在 build.gradle 文件或项目的构建文件中指定 APT 处理器。

annotationProcessor project(':apt-compiler-processor')

4、运行项目来激活 APT 处理器并让其开始处理。

    在 Eclipse 中你可以设置 Java Compiler > Annotation Processing 选项卡中的 Enable annotation processing 复选框来启用它,

    在 Intellij IDEA 中,你需要选择菜单中的 "Build" > "Rebuild project" 来刷新生成器任务列表。完成这些步骤后,就可以开始使用 AbstractProcessor 创建自己的注解处理器。

    在安卓中,直接构建项目即可。

5.3 安卓中,APT项目会不会增加apk的体积?

不会,processor的作用是在编译器解析注解、生成文件等,只在编译器用到,是不会打包进apk的。

更高级的用法可自行阅读开源项目,向大佬学习~

Demo 下载 :https://download.csdn.net/download/fumeidonga/87767415

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/566971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小红书数据分析:首播卖6亿,小红书直播开启新纪元!

5月22日&#xff0c;章小蕙在小红书开启了第一场带货直播。继董洁之后&#xff0c;小红书又迎来一位超级带货KOL。 据千瓜数据显示&#xff0c;相关话题#章小蕙小红书直播#上线不到30天&#xff0c;话题浏览量就高达2814.89万&#xff0c;笔记互动量达22.24万。 图 | 千瓜数据…

Android 12.0下拉状态栏通知栏的通知设置默认展开

1.概述 在12.0的产品定制化中,对于SystemUI的定制也是常用的功能,而在下拉状态栏中的通知栏部分也是极其重要的部分,每条通知实时更新在通知栏部分,由于通知栏高度的限制,每条通知是默认收缩的,功能开发需要要求通知默认展开,所以就要从通知的加载流程分析 如图: 2.…

化工生产制造企业实施技术变革前要问自己的 6 个问题

化工企业将新技术&#xff08;大数据、人工智能、云计算、RFID、PDA、GIS&#xff09;引入到业务中&#xff0c;通过填补技术基础设施中的空白来帮助企业团队成员更高效、更有效地完成工作&#xff0c;是一项卓有成就的事情。但是&#xff0c;企业在急于快速实施新解决方案以获…

数字化时代,如何规范化搭建数据仓库?

数据仓库作为商业智能BI系统中的一部分&#xff0c;已经成长为了企业信息化建设中必不可少的重要支撑&#xff0c;在可见的未来&#xff0c;数据仓库还会随着信息化、数字化技术、理念、应用的落地&#xff0c;继续成长。 数据仓库是一个面向主题的、集成的、随时间变化但信息…

纽约时报对全球HR选出他们最想招聘的毕业生所来自的前150所大学

美国《纽约时报》对全球2500个HR部门和2000位企业的CEO发出了问卷&#xff0c;排名选出他们最想招聘的毕业生所来自的大学 1哈佛大学。 美国 2耶鲁大学。 美国 3 剑桥大学 英国 4 牛津大学 英国 5 斯坦福大学。 美国 6 马萨诸塞州理工学院 美国 7 哥伦比亚大学。 美国 8 普林斯…

JavaEE(系列17) -- 线程安全的集合类

目录 1. 多线程环境使用ArrayList 1.1 使用同步机制 (synchronized 或者 ReentrantLock) 1.2 Collections.synchronizedList(new ArrayList); 1.3 使用 CopyOnWriteArrayList 2. 多线程环境使用队列 3. 多线程环境使用哈希表 3.1 Hashtable 3.2 ConcurrentHashMap 4. 相关面试…

北邮22信通:利用BF算法解决实际问题:题目实战(超详解)设计函数 char *locatesubstr(char *str1,char *str2)

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 获取更多文章 请访问专栏~ 北邮22信通_青山如墨雨如画的博客-CSDN博客 目录 题干描述 解析 1.string库函数 2.使用KMP算法思想 注解1 注解2 注解3 题…

Mysql基础 — DDL、DML、DQL、DCL、函数、约束

文章目录 Mysql基础一、数据模型1.1 关系型数据库与非关系型数据库1.2 Mysql 数据模型 二、SQL2.1 SQL 通用语法2.2 SQL分类2.3 DDL2.3.1 数据库操作2.3.2 表操作 — 创建 & 查询2.3.3 表操作— 修改&删除2.3.4 数据类型2.3.4.1 数值类型2.3.4.2 字符串类型2.3.4.3 日期…

LabImg安装与使用

一、LabImg安装 安装命令语句 pip install labelimg 或 conda install labelimg 后者&#xff0c;可以有效避免依赖库缺失情况。一般&#xff0c;第一个命令语句无法安装时&#xff0c;可以尝试用第二个命令语句。 示例 以管理员身份打开命令提示符窗口&#xff0c;输入…

js的堆栈和垃圾回收机制(gc)

js的堆栈和垃圾回收机制&#xff08;gc&#xff09; 本文目录 js的堆栈和垃圾回收机制&#xff08;gc&#xff09;堆栈深拷贝和浅拷贝实现深拷贝 垃圾回收机制栈溢出概念垃圾产生算法策略V8引擎的优化新生代&#xff1a;Scavenge 算法老生代&#xff1a;标记-清除-整理 算法 堆…

厚积薄发丨美格智能子公司众格智能荣获“张江之星”“闵行区企业技术中心”双项企业授牌

5月23日下午&#xff0c;2023年闵行科技节开幕式在大零号湾科创大厦举行。在开幕式“创蝶变智造”板块&#xff0c;美格智能技术股份有限公司全资子公司众格智能科技&#xff08;上海&#xff09;有限公司&#xff08;以下简称&#xff1a;众格智能&#xff09;入选“张江之星”…

加速数实融合,数据交易3.0模式上新

数据交易市场将迎来真正的突破&#xff1f; 目前看的确如此。随着去年底“数据二十条”的颁布&#xff0c;业界普遍认为数据基础制度将加速走向落地与完善&#xff0c;数据要素化今年有望迎来全面提速&#xff0c;将极大促进数据交易市场走向规模化。 IDC预测&#xff0c;到2…

QMI8658 - 姿态传感器学习笔记 - Ⅲ

文章目录 1.复位1.1 上电复位&#xff1a;1.2 推荐工作条件 2. 校准(COD)2.1 校准步骤2.2 校准注意事项&#xff1a;2.3 校准状态指示2.4 校准参数更新 3. 自检3.1 加速度计自检3.2 陀螺仪自检 4. Ctrl94.1 写Ctrl94.2 读Ctrl94.3 Ctrl9详细命令说明 5. 中断5.1 同步采样模式5.…

直接缓存访问DCA

直接缓存访问DCA&#xff1a;网卡原本DMA写是将接收到的数据帧写入系统内存&#xff0c;DCA机制是网卡DMA写输入的数据能直接发送到属于CPU内部的L2高速缓存中&#xff0c;从而提高网络IO的性能。 设备驱动程序要初始化网卡的DCA功能&#xff0c;将CPU ID号&#xff08;通过获取…

CentOS安装Whisper | RedHat安装Whisper | 服务器上安装Whisper

1、升级Python版本&#xff08;可选&#xff09; CentOS 7.9 系统默认安装的Python版本有两个&#xff0c;如果输入python -V我们看到的是python2的版本信息&#xff0c;如果我们输入python3 -V我们看到的是python3的版本信息。 由于whisper要求使用的是python版本是在3.8到3…

Linux(Ubuntu)安装和使用Edge浏览器

全文内容完全由AI创作&#xff0c;并通过人工核验&#xff0c;感谢各位的阅读。 Microsoft Edge是一款现代化的浏览器&#xff0c;它拥有众多功能和强大的性能&#xff0c;为用户带来更加流畅的浏览体验。 Edge最近推出了分屏功能&#xff0c;支持一个窗口同时显示两个选项卡&…

MySQL基础 — 多表查询以及事务管理

文章目录 MySQL基础 — 多表查询以及事务管理一、多表查询1.1 对应关系1.2 准备数据1.3 概述1.4 内连接1.5 外连接1.6 自连接1.7 联合查询 union1.8 子查询1.8.1 标量子查询1.8.2 列子查询1.8.3 行子查询1.8.4 表子查询 二、事务2.1 简介2.2 操作演示2.3 控制事务2.3.1 控制事务…

深入浅出Vite:深入理解 Rollup 的插件机制

上一节我们学会了 Rollup 构建工具的使用&#xff0c;相信你已经对 Rollup 的基础概念和使用有了基本的掌握。同时我们也知道&#xff0c;仅仅使用 Rollup 内置的打包能力很难满足项目日益复杂的构建需求。对于一个真实的项目构建场景来说&#xff0c;我们还需要考虑到模块打包…

如何通过CRM系统做好客户的分级分类

随着市场竞争的不断加剧&#xff0c;尤其是以客户为中心时代的到来&#xff0c;企业越来越注重客户的管理和服务。而CRM系统&#xff0c;作为企业客户管理的重要工具&#xff0c;其核心任务是对客户进行分级分类&#xff0c;以便更好地为客户提供定制化的服务。 客户之间的价值…

在 Docker 容器中安装 nvm

目录 前言一、步骤1.下载nvm安装脚本2.激活nvm3.安装Node.js4.验证 Node.js 是否安装成功5.如果需要安装特定版本的 Node.js&#xff0c;可以使用以下命令6.如果需要使用特定版本的 Node.js&#xff0c;可以使用以下命令6.验证特定版本的 Node.js 是否安装成功 总结 前言 提示…