框架开发使用注解处理器APT优雅提效

news2024/11/28 1:40:13

目录

  • 概述
  • 1.什么是注解处理器APT
  • 2.应用场景
  • 3.如何使用
    • 3.1 创建注解API模块
    • 3.2 创建注解处理器模块
    • 3.3 使用注解

概述

在现在的很多开源框架中,我们经常能在源码中看到注解处理器的影子,比如我们熟悉的阿里的ARouter,Android开发中的替代findViewById神器黄油刀ButterKnif,事件总线EventBus等都使用了注解处理器APT技术,下图是ARouter的项目结构图部分截图:
在这里插入图片描述
图中红圈部分就是ARouter注解处理器技术的实现模块。使用注解处理器还有一个好处就是可以解决部分功能因反射而带来的损耗问题,注意,这里不是说注解处理器的技术是取代反射的哈,我们使用反射其实就是想在程序运行期间动态获取到某个对象,再操作这个对象的对应方法完成我们想要实现的功能。但是这个过程是耗时的。而注解处理器是在编译时生成我们想要实现的功能的对应代码,比较典型的就是EventBus的实现从反射技术转为注解处理器的技术的应用。接下来,本文会介绍什么是注解处理器和如何使用注解处理器优雅的提高我们的开发效率。

1.什么是注解处理器APT

注解处理器(Annotation Process Tool)顾名思义就是一种处理注解的工具,它可以极大的优化我们平时写的冗余代码,比较典型的就是Android开发者经常写的findViewById,这类代码基本都是差不多的,人工去写不仅多,而且还容易出错,使用注解处理器技术优化后就只需要两行代码:

  @DIView(value = R.id.tv_text)
    TextView textView;

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

如上面的代码中,@DIView(value = R.id.tv_text)就是注解处理器的一部分,这个注解就是提供给调用方使用的,然后在自定义的注解中传入关键的参数值,注解处理器拿到这个关键的参数值后再将一些重复性高的代码统一生成,最后再和打包到应用程序中,所以我们在使用APT技术的时候的自定义注解都是设置在编译期间有效。如ARounter的自定义注解Route的有效范围就是编译期间有效:@Retention(RetentionPolicy.CLASS)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();

    /**
     * Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
     */
    String group() default "";

    /**
     * Name of route, used to generate javadoc.
     */
    String name() default "";

    /**
     * Extra data, can be set by user.
     * Ps. U should use the integer num sign the switch, by bits. 10001010101010
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * The priority of route.
     */
    int priority() default -1;
}

其实很好理解,因为我们使用APT技术主要是为我们在编译期间生成一些我们不想写的重复性代码,当代码生成完后,这些注解也就完成了它们的工作。除非这些注解在代码运行的时候还需要反射使用,否则我们都没有必要将这些注解范围定义成运行时有效。

至此,相信读者已经明白注解处理器是做啥的了,用一句话概括就是,注解处理器是处理我们自定义注解的工具,它帮助我们处理生成那些重复性比较高的代码,让我们不用去关心和处理那些重复的逻辑。

2.应用场景

那么注解处理器的应用场景主要有哪些呢,其实注解处理器是为了解放开发者的,它是一个面向开发者的工具,它的主要应用场景就是做框架的开发,例如阿里的ARouter路由框架,网络请求框架Retrofit,事件总线EventBus等,所以APT注解处理器技术是一个简化我们开发的工具,我们开发框架的时候,可以提取出框架中的很多逻辑差不多,但又大量重复的内容,使用注解处理器去优化。对注解处理器感兴趣的读者,建议去阅读下上面提到的几个框架源码,写的特别好。

3.如何使用

接下来,到了最关键的时刻了,说一千道一万,不会使用就完蛋,接下来利用我在视频网站上学到的一个类似黄油刀的例子,来介绍APT技术的使用方法。我们只实现一个功能,就是使用我们定义的注解去注解Android的View,然后直接使用这个View,如下所示:

@DIActivity
public class MainActivity extends AppCompatActivity {
    @DIView(value = R.id.tv_text)
    TextView textView;

    @DIView(value = R.id.tv_text_1)
    TextView textView1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // bindView方法其实就是取代findViewById的替代
        DIMainActivity.bindView(this);

        textView.setText("hello apt!!!!");

        textView1.setText("hello apt again!!!!");
        textView1.setTextSize(15);
    }
}

我们使用一个DIActivity注解我们的类,然后使用@DIView(value = R.id.tv_text) 注解我们声明的View,然后直接在程序中使用被我们注解的属性。然后框架就能帮我们实现这个对应的findViewByID 的逻辑。接下来是实现这个功能的步骤:

3.1 创建注解API模块

这里我们需要新建一个Java Library模块,记住一定是Java Library,这个模块主要是用于做自定义注解的定义,给注解处理器和调用者使用。
在这里插入图片描述
然后定义好我们需要使用到的自定义注解

DIActivity 注解用于我们要使用的Activity上,我们就是通过它来将生成的代码创建出的对象赋值给我们使用的Activity的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface DIActivity {

}

DIView 用于我们要使用的View上,调用者只需使用这个注解传入View的ID值就可以得到一个View对象了

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DIView {
    int value() default 0;
}

3.2 创建注解处理器模块

创建完自定义注解类,我们接下来就是对注解处理器模块的介绍了,框架的主要工作都是在注解处理器内完成,我们同样新建一个Java Library模块,用于处理我们定义的注解。如下图所示
在这里插入图片描述
我们需要注意的是在这个模块中,我们需要去引入对应的库依赖,来协助我们完成一些复杂繁琐的工作,我们需要引入下面几个库:

    implementation project(path: ':annotation') // 我们定义的注解模块,因为要处理注解,
      // 所以必须依赖上
    // auto-service 是Google提供的,辅助我们开发注解处理器
   
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
    // 这个是一个代码生成框架,利用它可以优雅的生成我们想要的代码
    implementation 'com.squareup:javapoet:1.10.0'

注意:引入com.google.auto.service:auto-service:1.0-rc7这个依赖时使用的是annotationProcessor ,需要注意,不然不会报错,但是也无法生成我们想要的代码
配置好了就可以进行开发了,处理前面注解的代码如下:

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DIProcessor extends AbstractProcessor {
private Elements elementUtils;
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(DIActivity.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> set,
     RoundEnvironment roundEnvironment) 
    {
        Set<? extends Element> elements = 
        roundEnvironment.getElementsAnnotatedWith(DIActivity.class);
        for (Element element:elements){
            // 判断是否为class
            TypeElement typeElement = (TypeElement) element;
            List<? extends Element> allMembers = 
            elementUtils.getAllMembers(typeElement);
            MethodSpec.Builder bindViewMethodSpecBuilder = 
            MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC,Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addParameter(ClassName.get(typeElement.asType()),"activity");

            for(Element item :allMembers){
                DIView diView = item.getAnnotation(DIView.class);
                if (diView == null) {
                    continue;
                }

                bindViewMethodSpecBuilder.addStatement(String.format("activity.%s = (%s)" 
                +"activity.findViewById(%s)",item.getSimpleName(),
                ClassName.get(item.asType()).toString(),diView.value()+""));
            }

            TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();

            JavaFile javaFile = 
            JavaFile.builder(getPackageName(typeElement),typeSpec).build();
            try {
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    private String getPackageName(TypeElement typeElement) {
        return elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        elementUtils = processingEnv.getElementUtils();
        super.init(processingEnv);
    }
}

上面的代码很简单,大致意思就是,使用工具类,获取注解的信息,然后生成我们想要的代码,最后写到文件里面。当我们要使用对应功能的时候,直接在程序中调用就可以了。在上面代码中出现的如下代码块:

TypeSpec typeSpec = TypeSpec.classBuilder("DI" + element.getSimpleName())
                    .superclass(TypeName.get(typeElement.asType()))
                    .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
                    .addMethod(bindViewMethodSpecBuilder.build())
                    .build();

就是com.squareup:javapoet:1.10.0框架干的事情,以面向对象的方式生成代码,如果没有这个框架,那么我们需要使用字符串来拼接出我们想要生成的类。比如EventBus的注解处理器就是用的字符拼接:
在这里插入图片描述

3.3 使用注解

接下来是使用注解,使用之前我们需要引入相关的依赖,首先,我们需要使用我们自定义的注解,所以需要引入注解模块的依赖,其次我我们需要依赖注解处理器模块,如下所示:

  implementation project(':annotation')
  annotationProcessor project(':annotation-processor')

注意:引用注解处理器模块时要用:annotationProcessor project(':annotation-processor'),是annotationProcessor 不是Implementation,如果使用错误会导致无法生成我们想要的目标代码

接下来就是使用注解就可以了。

@DIActivity
public class MainActivity extends AppCompatActivity {
    @DIView(value = R.id.tv_text)
    TextView textView;

    @DIView(value = R.id.tv_text_1)
    TextView textView1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // bindView方法其实就是取代findViewById的替代
        DIMainActivity.bindView(this);

        textView.setText("hello apt!!!!");

        textView1.setText("hello apt again!!!!");
        textView1.setTextSize(15);
    }
}

当我们编译代码的时候,会在下图路径中生成我们在注解处理器中拼接出的类,看到类的内容其实我们就理解了框架帮我们干的事情了。如果没有生成我们想要生成的类,建议读者去检查下自己的代码中是否又错误,引入依赖是否引错。

在这里插入图片描述至此,注解处理器APT的内容就讲完了,读者还是很有必要了解这个内容的,因为现在很多框架中都使用这个技术,项目的源码比较简单,这里就不提供了,需要的可以评论区留言,留下邮箱,建议读者手动去实现一遍。

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

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

相关文章

【git】git以及可视化界面下载安装

git 以及可视化界面下载安装 git下载安装测试功能 sourceTree下载安装 git 下载安装 下载地址 git官网上有多个版本&#xff0c;点击“Click here to download” &#xff0c;下载下来之后&#xff0c;一直下一步安装即可 测试功能 在任意文件夹中右击&#xff0c;看到图中…

Linux三剑客

前言 关于bash&#xff1a; bash&#xff1a;命令处理器&#xff0c;运行在文本窗口&#xff0c;能够执行用户输入的命令。 脚本&#xff1a;从linux文件中读取命令&#xff0c;被称为脚本。 1 命令&#xff1a;alias&#xff1a;起别名 2 快捷键操作&#xff1a; ctrla&#…

浅谈如何提高自动化测试的稳定性和可维护性

目录 前言&#xff1a; 装饰器与出错重试机制 什么是装饰器&#xff1f; 编写一个出错重试装饰器 pytest 里的出错重试机制实现 Allure 里的测试用例分层 为什么要采用分层机制&#xff1f; allure 的装饰器step 前言&#xff1a; 自动化测试在软件开发中扮演着重要的…

Fortran lapack求数组的特征值,特征向量

call zgeev(V, V, n, arr, lda, w, vl, ldvl, vr, ldvr, work, lwork, rwork, info) 这个函数是求矩阵的特征值&#xff0c;且结果是双精度复数的情况&#xff0c;具体可以查MKL的官方文档。 如果是单精度复数就要用cgeev&#xff0c;其中的参数也是将双精度改为单精度即可。…

Hive,FineBI-30W聊天数据分析及可视化-B站黑马学习记录

2023B站黑马Hadoop、Hive、云平台实战项目 目录 1. 清洗数据 2. 计算各指标&#xff0c;并创建表存储结果 3.FineBI连接Hive数据库&#xff0c;将指标结果可视化 1. 清洗数据 1&#xff09;部分数据缺失地理位置信息&#xff08;sender_gps&#xff09;&#xff0c;需要剔…

Linux下的调试器——gdb使用指南

文章目录 一.序二.安装gdb调试器三.进入调试四.调试相关指令 前言&#xff1b; 在VS环境下&#xff0c;我们不仅可以写代码、编译、运行可执行程序&#xff0c;还可以对生成的可执行程序进行调试。本章我们就来学习如何在Linux环境下进行调试。 一.序 要进行调试&#xff0c;首…

EPICS一个示例数据库实例详解

以下是一个示例数据库图表&#xff1a; 以上记录的数据库文件如下&#xff1a; record(ao, "$(P):SET") {field(FLNK, "$(P):ACTIVATE")field(VAL, "2")field(OUT, "$(P):RUN")field(DRVH, "40")field(DRVL, "5"…

摩尔投票算法(Moore‘s Voting Algorithm)及例题

摩尔投票算法&#xff08;Moores Voting Algorithm&#xff09;及例题 摩尔投票算法简介摩尔投票算法算法思想摩尔投票算法经典题目169. 多数元素229. 多数元素 II6927. 合法分割的最小下标 上午打力扣第 354 场周赛最后十五分钟用摩尔投票算法顺利 AC 第三题&#xff0c;以前没…

ViewRootImpl简析

ViewRootImpl简析 如何实现视图和wms沟通桥梁的作用Session的创建获取画布如何实现事件分发的桥梁作用 The top of a view hierarchy, implementing the needed protocol between View and the WindowManager. This is for the most part an internal implementation detail of…

快速搭建Python(Django)+Vue环境并实现页面登录注册功能

文章目录 一. 创建vue项目及环境搭建1. 创建vue项目2. 配置axios3. 创建vue组件login和register4. 设置并引用路由vue-router5. 完成login&#xff0c;register组件代码6. 完成App.vue的代码 二. 创建django项目及环境搭建1. 创建django项目2.配置mysql数据库3. 创建应用app4.创…

学习babylon.js --- [4] 体验WebVR

本文基于babylonjs来创建一个简单的WebVR工程&#xff0c;来体验一下babylonjs带来的VR效果&#xff0c;由于没有VR头显&#xff0c;所以只能使用Win10自带的混合现实模拟器&#xff0c;开启模拟器请参考这篇文章 一 简单工程 本文基于第三篇文章中的工程进行修改&#xff0c;…

Deep Residual Learning for Image Recognition

2015-ResNet 关键词: residual connection CV论文:一般在第一页就放一个很“好看”的图 从图可以得到的结论:深的网络更难训练 ( 不仅因为过拟合,因为训练时误差都很难降低 ) 网络结构图 1*1卷积作用:改变通道数 ,提出维度匹配方法:1)补零;2)投影projection…

map和set的模拟实现|利用红黑树封装map和set|STL源码剖析

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量干货博客汇总https://blog.csdn.net/yu_cblog/c…

再战Nvidia,安装 Windows 11 和 EndeavourOS 双系统

吐血刚装了一周的Ubuntu23.04就挂了&#xff0c;由于买的是最新的显卡就上了Test版本&#xff0c;结果Ubuntu自带的nvidia驱动535居然会失灵&#xff0c;nvidia-smi直接fail。于是换了一堆发行版本&#xff0c;但是我这个主板没带hdmi不支持集显输出&#xff0c;必须安装时就上…

WEB:warmup

背景知识 文件包含 目录遍历 代码审计 题目 打开链接后只有一张图片 f12和查看源代码都试一下&#xff0c;查看源代码发现如下图 访问一下source.php&#xff0c;有如下代码&#xff0c;进行代码审计 <?phphighlight_file(__FILE__);class emmm{public static function c…

#pragma pack不成对出现造成的异常

#pragma pack需要成对出现&#xff0c;若不成对出现会造成异常&#xff0c;难以调查。 现有5个文件&#xff0c;分别是main.c&#xff0c;A.h&#xff0c;A.c&#xff0c;B.h&#xff0c;B.c。 A.h文件内容如下&#xff1a; #include <stdio.h> #include <stdlib.h&g…

ylb-接口14购买理财产品

总览&#xff1a; 在api模块service包&#xff0c;Invest类下添加&#xff08;投资理财产品&#xff0c; int 是投资的结果 &#xff0c; 1 投资成功&#xff09;&#xff1a; package com.bjpowernode.api.service;import com.bjpowernode.api.pojo.BidInfoProduct;import j…

go mod vendor简明介绍

Go 语言在 go 1.6 版本以后编译 go 代码会优先从 vendor 目录先寻找依赖包&#xff0c;它具有以下优点&#xff1a; 复制依赖&#xff1a;go mod vendor 会把程序所依赖的所有包复制到项目目录下的vendor 文件夹中&#xff0c;所以即使这些依赖包在外部源&#xff08;如 GitHu…

MySQL 约束、聚合查询和联合查询练习

1. 数据库约束 设置一个考勤系统, 包含员工表&#xff0c;考勤记录表 首先要明白, 员工表对考勤记录表是一对多 员工表中起码包含 id , name 考勤记录表则包含 id, 日期, 考勤记录 考勤记录表为子表, id 则为外键约束 员工表为父键, id则为主键约束 搞明白这些, 接下来就…

【C语言】矩阵相乘

#include <stdio.h>#define M 3 #define N 4 #define P 3void fun(int a[M][N], int b[N][P], int m, int p, int n)//定义函数fun {printf("m: %d, p: %d, n: %d\n", m, p, n);int c[M][P]{0};int i, j, k;for(i 0; i < m; i){for(j 0; j < n; j){fo…