android注解之APT和javapoet

news2024/11/23 13:25:41

前言

前面我们已经讲过注解的基本知识,对于注解还不太了解的,可以去看一下之前的文章,

android 注解详解_袁震的博客-CSDN博客。

之前我们在讲注解的时候,提到过APT和JavaPoet,那么什么是APT和JavaPoet呢?下面我们来详细讲解一下。

1,APT是什么?

APT,英文全称Annotation Processing Tool,是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码, 如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。注意,是在编译期间

简单来讲,就是根据我们定义的注释规则,帮助我们生成代码,生成类文件。

2,APT中的元素

在APT中,它会分为包元素,类元素,属性元素,方法元素。那么,这些元素的意义是什么呢?

 首先,我们需要明白APT真正的作用是什么。就拿ButterKnife来说,他真正要实现的就是我们通过BindView,把id传给注解,然后就会在编译时动态生成很多类,专门去处理你绑定的这些id,从而达到你只需要几行代码就能实现绑定,点击事件等功能。那为什么不能直接就写好类去处理,而非要到编译期去自动生成类处理呢?因为它不知道你会传哪些id,所以需要动态的生成。

所以,我认为APT的主要作用就是帮你动态生成类。

import androidx.appcompat.app.AppCompatActivity;//PackageElement 包元素/节点

public class MainActivity2 extends AppCompatActivity { // TypeElement 类元素/节点

    private int a;// VariableElement 属性元素/节点

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {// ExecuteableElement 方法元素/节点
        super.onCreate(savedInstanceState);
    }
}

这些元素的意义就是,它们会提供相关信息,来帮助你后面生成类。

包元素
PackageElement
表示一个包程序元素。提供对有关包及其成员的信息的访问
方法元素
ExecutableElement
表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
类元素
TypeElement
表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
属性元素
VariableElement
表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

3,APT中常用的API

在AbstractProcessor中,有两个方法是核心方法:

    
//初始化工作,主要做一些准备工作
public synchronized void init(ProcessingEnvironment processingEnv) {
    
}

//处理注解 核心方法
//annotations 使用了支持处理注解的节点集合
//roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解
//return true 表示后续处理器不会再处理(已经处理完成)
public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);

上面ProcessingEnvironment中常用的api如下:

getElementUtils()获取操作Element的工具类
getMessager()获取Messager,用来打印日志相关信息
getFiler()获取文件生成器,类等最终要生成的文件,都是通过生成器生成的
getTypeUtils()获取类信息的工具类,用于操作TypeMirror的工具方法
getOptions()主要用来接收应用传过来的数据

上面RoundEnvironment常用api如下:

//获取所有被@YuanZhen注解的元素集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);

Element常用api如下:

getEnclosedElements()
返回该元素直接包含的子元素
getEnclosingElement()
返回包含该element的父element,与上一个方法相反
getKind()
返回element的类型,判断是哪种element
getModifiers()
获取修饰关键字,入public static final等关键字
getSimpleName()
获取名字,不带包名
getQualifiedName()
获取全名,如果是类的话,包含完整的包名路径
getParameters()
获取方法的参数元素,每个元素是一个VariableElement
getReturnType()
获取方法元素的返回值
getConstantValue()
如果属性变量被final修饰,则可以使用该方法获取它的值

4,APT环境搭建

4.1新建注解javalib,命名为compiler

4.2在compiler的buidl.gradle中添加依赖,用来注册注解处理器

compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'

4.3新建AnnotationProcessor

新建MyAnnotationProcessor类,继承AnnotationProcessor

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)

// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})

// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment       roundEnvironment) {
        
        return false;
    }
}

4.4新建注解javalib  命名为yuanzhenannotation

4.5创建自己的注解

@Target(ElementType.TYPE) //作用与类上
@Retention(RetentionPolicy.SOURCE) //在编译时期生效
public @interface YuanZhen {

    String value();//一个默认值
}

4.6添加依赖

在compiler中依赖yuanzhenannotation

在app中添加依赖:

4.7在app中使用注解:

@YuanZhen("study")
public class MainActivity extends AppCompatActivity {

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

至此,APT环境搭建完成

5,APT获取注解

在MyAnnotationProcessor类中:

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)

// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})

// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {

    private Messager messager;// 用来打印日志相关信息
    private Elements elementUtils;// 操作Element的工具类(类,函数,属性,其实都是Element)
    private Filer filer;//文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Types typeUtils;// type(类信息)的工具类,包含用于操作TypeMirror的工具方法

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        typeUtils = processingEnv.getTypeUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //因为javalib没有Log,所以我们使用messager来打印
        messager.printMessage(Diagnostic.Kind.NOTE,"aaaaaaZZZZZ");//打印 
        //获取所有被@YuanZhen注解的元素集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);
        for (Element element : elements) {
            String className =element.getSimpleName().toString();//获取元素名
            messager.printMessage(Diagnostic.Kind.NOTE,"-----="+className);//打印类名
            YuanZhen annotation = element.getAnnotation(YuanZhen.class);//获取注解
            messager.printMessage(Diagnostic.Kind.NOTE,"-----value="+annotation.value());//打印注解参数
        }
        return true;
    }
}

具体的api在上文已经有个介绍,下面看Build日志输出:

参数study已经传递过来,类名MainActivity也已经获取到。

6,传统的生成类的方式

上面已经完成了APT环境的配置,下面就是动态生成类了,在javapoet之前,传统的生成类的方式就是采用字符串拼接的样式。最典型的应用就是EventBus。

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("/** This class is generated by EventBus, do not edit. */\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
            }
        }
    }
}

通过上面可以看到,Eventbus是通过字符串拼接的形式来生成类的,这种方式虽然比较简单,但是也不是一件轻松的体力活,不符合OOP的编程思想。下面我们就来介绍一下现在最流行的写法,采用javapoet。

7,JavaPoet简介

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法, 可以很方便的使用它根据注解生成对应代码。 通过这种自动化生成代码的方式, 可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

8,JavaPoet中常用的类

MethodSpec代表一个构造函数或方法声明
TypeSpec代表一个类,接口,或者枚举声明
FieldSpec代表一个成员变量,一个字段声明
JavaFile包含一个顶级类的Java文件
ParameterSpec用来创建参数
AnnotationSpec用来创建注解
ClassName用来包装一个类
TypeName类型,如在添加返回值类型是使用 TypeName.VOI

$S 字符串,如:$S, ”hello

$T 类、接口,如:$T, MainActivit

9,JavaPoet生成类的思想

传统模式生成类的思想是先写包,然后类,然后方法。

JavaPoet的思想恰恰相反,它是先方法,然后类,然后包。

10,JavaPoet 使用

10.1 导包

在complier的build.gradle里面导包

 implementation "com.squareup:javapoet:1.9.0"

10.2写出要生成的class

/**
package com.yuanzhen.yuanzhenannotation;

public class MyClass {

    public static void main(String[] args) {
        System.out.println("Hello, yuanzhen");
    }
}
*/

10.3 通过javapoet生成该类

 // 1.方法 
MethodSpec mainMethod = MethodSpec.methodBuilder("main")//添加方法名
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//添加修饰符
                .returns(void.class)//添加返回值
                .addParameter(String[].class, "args")//添加方法参数
                .addStatement("$T.out.println($S)", System.class, "Hello, YuanZhen!")//添加内容
                .build();
        // 2.类
        TypeSpec helloWorld = TypeSpec.classBuilder("MyClass1")//添加类名
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//添加修饰符
                .addMethod(mainMethod)//添加方法
                .build();
        // 3.包
        JavaFile packagef = JavaFile.builder("com.yuanzhen.apt1", helloWorld).build();
        // 去生成
        try {
            packagef.writeTo(filer);
            messager.printMessage(Diagnostic.Kind.NOTE, "success...");
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "error...");
        }

10.4在app build中查看生成的文件

11,总结

关于APT和JavaPoet的使用就讲完了,利用这个技术,我们可以实现很多强大的功能。

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

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

相关文章

pprof火焰图性能优化

pprof火焰图性能优化 火焰图&#xff08;flame graph&#xff09;是性能分析的利器,在go1.1之前的版本我们需要借助go-torch生成,在go1.1后go tool pprof集成了此功能,今天就来说说如何使用其进行性能优化 在你启动http server的地方直接加入导入: _ “net/http/pprof” 获取…

如何在 7 分钟内黑掉 40 个网站

这将详细讲述我如何侵入托管 40 个(这是一个确切数字)网站的服务器以及我的发现。 注意:需要一些必备的 CS 知识才能理解本文的技术部分。 一位朋友给我发消息说他的网站发现了XSS 漏洞,希望我进一步查看。这是一个重要的阶段,因为我倾向于要求他正式表示我已获得他的许可…

Mojo 摸脚语言,似乎已经可以安装

文章原地址&#xff1a;https://i.scwy.net/it/2023/090821-mojo/ Mojo 吹得很凶&#xff0c;面向AI编程&#xff0c;甩Python几十条街&#xff0c;融资上亿.... 但无缘一试&#xff0c;在Win和Ubuntu上试都不能通过。 由 LLVM 和 Swift 编程语言的联合创始人 Chris Lattner…

荣耀崛起阵容推荐,荣耀崛起最强阵容

今天给大家带来的荣耀崛起阵容推荐是新手阵容推荐&#xff0c;以核心输出为点&#xff0c;由点及面&#xff0c;来展开叙述阵容&#xff01; 关注【娱乐天梯】&#xff0c;获取荣耀崛起0.1折内部福利号 荣耀崛起最强阵容兽族战神流&#xff1a; 此阵容是以战士为核心&#xff0…

pycharm使用

在使用pycharm时&#xff0c;有时一个回车或者一个tab键&#xff0c;缩进的长度不符合预期可以调整设置tab键缩进的长度&#xff1a; 平时工作中&#xff0c;不同的人在编辑代码缩进的时候&#xff0c;有的人喜欢按四个或者六个空格&#xff0c;有的人喜欢按tab键&#xff0c;而…

网络变压器/网络滤波器(脉冲变压器)选型/定制要明确的要求

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;1,做电路板项目开发的工程师有时会遇到开发标的的空间限制&#xff0c;要对电子元器件的占用空间进行优化&#xff0c;同样电性参数的产品有的产品选择性比较小,只有部分产品有可选择性,这就给工程师进行板子空间优…

OJ练习第167题——单词接龙

单词接龙 力扣链接&#xff1a;127. 单词接龙 题目描述 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk&#xff1a; 每一对相邻的单词只差一个字母。 对于 1 < i < k 时&…

ES快速学习

ES 用于&#xff1a;存储、分析、检索 的工具 组成及与类比到mysql组成 倒排索引

Redis 介绍安装

数据库 关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。 SQL 语句&#xff08;标准数据查询语言&#xff09;就是一种基于关系型数据库的语言&#xff0c;用于执行对关系型…

Spring Bean的生命周期及三级缓存流程

Spring Bean 的生命周期说起来其实就三个大块&#xff1a;实例化Bean -> 设置属性(注入和装配) -> 初始化 -> 使用Bean -> 销毁Bean 这个很好理解&#xff0c;但是内部是怎么样注入&#xff0c;初始化以及销毁&#xff1f;经历怎么样的过程呢&#xff1f;追随这些问…

LeetCode(力扣)332.重新安排行程Python

LeetCode332.重新安排行程 题目链接代码 题目链接 https://leetcode.cn/problems/reconstruct-itinerary/ 代码 class Solution:def backtracking(self, tickets, used, cur, result, path):if len(path) len(tickets) 1:result.append(path[:])return Truefor i, ticket…

软件设计开发笔记4:QT操作SQLite数据库

有时候我们需要在软件中记录一些历史数据以便于对数据的查询。而我们希望软件不能太复杂&#xff0c;体量也不要太大&#xff0c;这个时候就需要如SQLite这样轻量级的数据库。这篇中我们就来讨论如何在使用QT开发应用是操作SQLite数据库。 0、概述 SQLite是一款开源、轻量级、…

SpringBoot原理-自动配置-原理分析-@Conditional

前言 在自动配置类中声明Bean的时候&#xff0c;除了在方法上添加Bean注解&#xff0c;还会加上Conditionalxxx的注解&#xff08;该注解都是条件装配的注解&#xff09; Conditional 作用&#xff1a;按照一定的条件进行判断&#xff0c;在满足给定条件后才会注册对应的bea…

CSP 202203-1 未初始化警告

答题 要注意是xi和yi的范围&#xff0c;yi可以是0为常数。 #include<iostream> using namespace std;int main() {int n,k;cin>>n>>k;bool*initializenew bool[n]{false};int result0,x,y;while(k--){cin>>x>>y;if(y&&!initialize[y-1…

E. Hanging Hearts

Problem - E - Codeforces 思路&#xff1a;我们考虑用树形dp&#xff0c;用f[i][0]表示以i为根&#xff0c;并且当前节点不在最长上升子序列中&#xff0c;用f[i][1]表示以i为根&#xff0c;当前节点在最长上升子序列中&#xff0c;那么f[i][0]max(f[j][0],f[j][1])&#xff0…

4年经验来面试20K的测试岗,连基础都不会,还不如招应届生!

公司前段时间缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在10-20k&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。 看简历很多都是3、4年工作经验&#xf…

使用Python 进行分析

在当今竞争激烈的互联网时代&#xff0c;对于网站的SEO优化至关重要。本文将介绍一种强大的秘密武器&#xff1a;使用Python 进行竞争对手网站分析。通过这种技术&#xff0c;您可以深入了解竞争对手的网站结构、关键词排名和优化策略&#xff0c;为您的SEO优化工作提供有力支持…

网络原理

网络原理 传输层 UDP 特点 特点&#xff1a;无连接&#xff0c;不可靠&#xff0c;面向数据报&#xff0c;全双工 格式 怎么进行校验呢&#xff1f; 把UDP数据报中的源端口&#xff0c;目的端口&#xff0c;UDP报文长度的每个字节&#xff0c;都依次进行累加 把累加结果&a…

跨境电商产业链,服务商的“霸道”你见识过吗?(测评补单)

跨境电商行业的服务商众多&#xff0c;涉及到从前期培训和店铺注册准备到中期选品软件、营销服务、流量投放和支付等多个环节。然而&#xff0c;行业乱象也日益严重&#xff0c;出现了一些不良现象&#xff0c;如恶意竞争、高价要求、割韭菜等。 卖家在选择服务商时应谨慎&…

自适应t分布变异的黏菌优化算法,MATLAB代码

本期为大家带来的是&#xff1a;自适应t分布变异的黏菌优化算法。分别在CEC2005,CEC2017,CEC2021和CEC2022上进行测试&#xff0c;自适应t分布变异的黏菌优化算法(DTSMA)均有非常不错的表现&#xff01;大家可以将此文章中提到的改进策略用于别的智能算法的改进。 参考文献&…