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

这篇具有很好参考价值的文章主要介绍了【Java 基础】Java SPI 二 之 Java APT原理及APT实战 - 一步步教你写ButterKnife。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、定义

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 创建一个项目,如下图

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

4.2 新建自定义注解

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

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

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

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->包名目录下)

如下图所示

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

该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')
}

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

 实现很简单,就是通过反射去调用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");
    }
}

运行代码截图

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

五、痛点及优化

我们可以看到,按官方文档一步步来写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'
}

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

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

六、一些疑问

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文章来源地址https://www.toymoban.com/news/detail-479012.html

到了这里,关于【Java 基础】Java SPI 二 之 Java APT原理及APT实战 - 一步步教你写ButterKnife的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 前几天瑶瑶子学习了Go语言的基础语法知识,那么今天我们就写个

    2024年02月06日
    浏览(53)
  • 正则表达式详解(零基础教学,手把手教你写正则)

    本篇文章将从零讲解什么是正则表达式,以及正则表达式的规则、在python中的应用,用通俗易懂的描述方式进行零基础级别的讲解,尽量做到全网最全讲解,力求最高质量文章,欢迎关注!点击目录可直接进行相关位置跳转。 目录: 什么是正则? 为什么需要正则? 元字符

    2023年04月08日
    浏览(41)
  • 【Java】手把手教你写学生信息管理系统(窗口化+MYSQL)

                (本项目使用到了数据库的可视化软件DataGrip,需要同学们自行下载并配置环境) 首先我们需要在DataGrip中建立一个student的框架                                                         然后建立一个studenttable表                   

    2024年02月04日
    浏览(39)
  • 【教你写爬虫】用Java爬虫爬取百度搜索结果!可爬10w+条!

    大家好,我是盆子。今天这篇文章来讲解一下:使用Java爬虫爬取百度搜索结果。 首先,展示爬取的数据,如下图。 爬取结果1: 爬取结果2: 代码爬取展示: 可以看到,上面爬取了五个字段,包括 标题,原文链接地址,链接来源,简介信息,发布时间。 用到的技术栈,主要有这

    2024年02月05日
    浏览(58)
  • CTFshow wbe41 教你写脚本

    这道题确实难,查了很多个大佬的题解,发现这道题考察的是或运算生成字符,但是过了n篇文章发现全是直接用羽师傅的脚本,没有人说这个脚本是怎么运行的,莫名其妙就出结果,脑瓜嗡嗡,可能大佬们默认这脚本大家一看就会用吧,小白只能自己研究了,于是我研究了羽

    2023年04月17日
    浏览(36)
  • 手把手教你写go单元测试

    ​ 在 Go 语言中,单元测试是一种测试方法,用于验证代码的某个独立单元是否按预期功能,它的目的是确保代码的每个组成部分都在独立测试的情况下运行正常。 ​ 在我们对项目新增一个新功能时,最好就要养成写单元测试的好习惯,这样可以有助于提高我们代码的质量、

    2024年04月14日
    浏览(44)
  • 【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(基础编程篇)

    Preconditions(前置条件):让方法调用的前置条件判断更简单 。 Guava在Preconditions 类中提供了若干前置条件判断的实用方法,我们强烈建议在 Eclipse 中静态导入这些方法。每个方法都有三个变种: 当方法没有额外参数时,抛出的异常中不包含错误消息,这会使得调用方很难确

    2024年02月07日
    浏览(69)
  • 手摸手教你写任务中心-积分领取&消耗&回收

    继上一篇签到任务之后呢, 就有朋友让我写一下任务积分的领取和使用, 以及回收; 其实前面两种都不难, 就只是积分的加减而已, 真正麻烦的是回收, 有回收的话你就需要考虑到每笔积分存在多种状态的可能了; 明细表(mysql) 记录积分的每一笔获取, 消耗的回收的记录, 并且回收

    2024年02月08日
    浏览(54)
  • 算法大神教你写代码实现回文字符串检测

    作者:禅与计算机程序设计艺术 很多公司面临着处理海量数据,因此需要对数据进行快速有效的分析和处理,其中数据清洗是一个非常重要的环节。如何快速准确地识别出文本中的所有回文子串,成为一个值得研究的热点问题。 在本篇教程中,我将给大家介绍一种基于滑动

    2024年02月06日
    浏览(41)
  • 手把手教你写stm32f103智能风扇

    本系统可以分为两个模式来进行运行,分别为手动模式和自动模式,同时,在上电进入系统后,还会有一个模式选择的界面产生。 模式选择:在此界面中,可以通过按键K1来控制模式选择,两个模式分别为手动模式和自动模式;通过按键K2可以进入模式。 手动模式:在手动模

    2023年04月17日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包