Android AnnotationProcessor

article/2025/10/14 14:44:49

Android AnnotationProcessor

  • 一.项目结构
  • 二.定义注解
  • 三.实现注解处理器
    • (一)依赖
    • (二)注解处理器
    • (三)处理注解
  • 四.使用注解处理器
    • (一)依赖
    • (二)使用注解
    • (三)生成的代码
  • 五.注意事项

注解处理器通常可以用在模块间解藕、自动生成代码等地方,比如router路由或者butterknife。效果就是我们在某些地方标注某些注解,在编译时,注解处理器会扫描这些注解的地方,然后生成一些代码,这样做可以实现全局的一些自动化功能,并不用开发者感知,非常方便,有点类似gradle插件。

这里以一个demo演示下通常需要怎么实现注解处理器的功能。
demo的功能很简单:通过注解的方式,自动生成接口和实现类的注册关系,在主项目里可以直接调用接口方法,不用注册。

一.项目结构

首先我们创建一个Android项目,添加两个module。
在这里插入图片描述

  • app:项目主module
  • lib_annotations:定义注解的java-module,通常会把一些功能注解,定义为一个aar
  • lib_compiler:定义注解处理器的java-module,不会被打入apk,只在编译时使用

二.定义注解

首先在lib_annotations里定义我们的注解。

@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.TYPE)
public @interface ByteService {Class<?> clazz() default Object.class;
}

这里定义了一个ByteService的注解类,有一个clazz参数用于指定对应的服务接口,默认为Object.class。
@Retention(RetentionPolicy.SOURCE)指定注解只保留在源文件中,不会被保留到class字节码文件中:因为在编译前期通过扫描源文件就使用完了注解。
@Target(value = ElementType.TYPE)指定该注解只能使用在类上:因为我们的功能是注册接口和实现类的关系。

三.实现注解处理器

接着在lib_compiler里定义你注解处理器。

(一)依赖

dependencies {implementation 'com.google.auto.service:auto-service:1.0-rc4'implementation 'com.squareup:javapoet:1.11.1'implementation project(':lib_annotations')
}

首先引入相关依赖:

  • auto-service:用于自动识别注解处理器的功能
  • javapoet:用于生成java代码的工具sdk
  • lib_annotations:要使用上面定义的注解,引入本地库

(二)注解处理器

然后定义处理类,处理类要继承自AbstractProcessor类。

@AutoService(Processor.class)
public class ByteProcessor extends AbstractProcessor {}

使用@AutoService(Processor.class)注解,会在编译时,由auto-service库在jar包的/java/main/META-INF/services/下生成一个javax.annotation.processing.Processor文件,内容就是注解处理器类的类名,也是后续编译器识别的标示。
在这里插入图片描述
在这里插入图片描述

(三)处理注解

我们来看看Processor的具体实现。

private Filer filer;
private Messager messager;
private Map<String, String> mapper = new HashMap<>();@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);filer = processingEnvironment.getFiler();messager = processingEnvironment.getMessager();
}

首先init()方法可以通过全局的ProcessingEnvironment环境对象,获取一些功能对象。

  • Filer:用于生成新的java文件的对象
  • Messager:用于输出log的对象
@Override
public Set<String> getSupportedAnnotationTypes() {Set<String> res = new HashSet<>();res.add(ByteService.class.getCanonicalName());return res;
}

getSupportedAnnotationTypes()方法用于返回该Processor想要接收处理的注解,要返回全路径类名,通常使用getCanonicalName()方法。该方法也可以通过在Processor类上定义SupportedAnnotationTypes注解的方式指定。

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {//获取该注解的元素Set<? extends Element> sets = roundEnvironment.getElementsAnnotatedWith(ByteService.class);if (sets != null && sets.size() > 0) {for (Element element : sets) {//每一个元素由于只能是类,所以都是TypeElement类型if (element instanceof TypeElement) {//获取定义你该注解的元素(这里是类)的全路径名称String implName = TypeName.get(element.asType()).toString();//对应的接口全路径类名String interName;try {//通过注解的clazz对象直接获取  interName = element.getAnnotation(ByteService.class).clazz().getCanonicalName();} catch (MirroredTypeException mte) {//由于调用clazz对象时,可能因为Class对象还没有被加载,所以抛异常//异常中有相关class对象的信息,直接拿到类名即可interName = TypeName.get(mte.getTypeMirror()).toString();}//如果没有定义你clazz(默认为Object),则取该类默认实现的接口if (Object.class.getCanonicalName().equals(interName)) {List<? extends TypeMirror> typeMirrors = ((TypeElement) element).getInterfaces();interName = TypeName.get(typeMirrors.get(0)).toString();}//放入map中后续生成代码mapper.put(interName, implName);//messager输出logmessager.printMessage(Diagnostic.Kind.NOTE, "Interface: " + interName + " Impl: " + implName);}}//生成代码generate();}return true;
}

注释已经解释的很清楚,大概分几步:

  1. 获取指定注解的所有元素
  2. 拿到元素的类名
  3. 拿到元素标注的接口的类名
  4. 存入map,输出log
  5. 开始生成代码
private void generate() {//private constructorMethodSpec cons = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();//static mapParameterizedTypeName mapType = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Class.class), ClassName.get(Object.class));FieldSpec map = FieldSpec.builder(mapType, "services", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("new java.util.HashMap<>()").build();//static initFieldSpec init = FieldSpec.builder(Boolean.class, "isInit", Modifier.PRIVATE, Modifier.STATIC).initializer("false").build();//static getServiceMethodSpec.Builder getServiceBuilder = MethodSpec.methodBuilder("getService").addModifiers(Modifier.PUBLIC, Modifier.STATIC);TypeVariableName t = TypeVariableName.get("T");TypeVariableName b = TypeVariableName.get("B").withBounds(t);getServiceBuilder.addTypeVariable(t).addTypeVariable(b);getServiceBuilder.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), t), "clazz");getServiceBuilder.returns(b);//statementgetServiceBuilder.beginControlFlow("if(!isInit)");generateInitStatement(getServiceBuilder).addStatement("isInit=true").endControlFlow();getServiceBuilder.addStatement("return (B) services.get(clazz)");//classTypeSpec typeSpec = TypeSpec.classBuilder("ServiceManager").addModifiers(Modifier.PUBLIC, Modifier.FINAL).addField(init).addField(map).addMethod(cons).addMethod(getServiceBuilder.build()).build();//fileJavaFile javaFile = JavaFile.builder("com.util.service", typeSpec).build();try {javaFile.writeTo(filer);} catch (IOException e) {}
}

生成代码主要是使用javapoet提供的功能:

  1. MethodSpec生成方法和构造器
  2. FieldSpec生成字段
  3. TypeVariableName生成泛型定义
  4. ParameterizedTypeName生成类型,可以包含泛型
  5. TypeSpec生成类
  6. JavaFile生成文件
  7. Filer提供写入文件功能

这里注册代码是通过generateInitStatement()方法,把map里的所有对象直接插入到代码里实现的。

private MethodSpec.Builder generateInitStatement(MethodSpec.Builder getServiceBuilder) {for (Map.Entry<String, String> entry : mapper.entrySet()) {getServiceBuilder.addStatement(String.format("services.put(%s.class,new %s())", entry.getKey(), entry.getValue()));}return getServiceBuilder;
}

至此就完成了注解处理器的定义。

四.使用注解处理器

完成类注解处理器,主module就可以使用了。

(一)依赖

dependencies {...annotationProcessor project(':lib_complier')implementation project(':lib_annotations')
}

我们要使用注解,所以要引入lib_annotations库。
引入注解处理器时,使用annotationProcessor即可,在编译时,会自动识别上面说的META-INF里的类名,找到类进行注解处理器的执行。

(二)使用注解

我们定义了两个接口和两个实现类。
在这里插入图片描述

@ByteService
public class Service1Impl implements IService1 {@Overridepublic void doFun() {System.out.println("doFun");}
}@ByteService(clazz = IService2.class)
public class Service2Impl implements ITest, IService2 {@Overridepublic void doTest() {System.out.println("doTest");}@Overridepublic void test() {System.out.println("test");}
}

其中Service1Impl由于只有一个接口所以采用默认的注解,Service2Impl有两个接口,指定注解的clazz接口为IService2。
在我们build一次后,就可以直接调用ServiceManager使用。

ServiceManager.getService(IService1.class).doFun();
ServiceManager.getService(IService2.class).doTest();

(三)生成的代码

而注解处理器生成的代码如下。

public final class ServiceManager {private static Boolean isInit = false;private static final Map<Class, Object> services = new java.util.HashMap<>();private ServiceManager() {}public static <T, B extends T> B getService(Class<T> clazz) {if(!isInit) {services.put(com.example.byteapplication.annotation_process.IService1.class,new com.example.byteapplication.annotation_process.Service1Impl());services.put(com.example.byteapplication.annotation_process.IService2.class,new com.example.byteapplication.annotation_process.Service2Impl());isInit=true;}return (B) services.get(clazz);}
}

五.注意事项

  1. javapoet生成类时,需要注意import相关类,或者通过全路径类名使用参考文章
  2. 注解处理器也可以debug调试参考文章
  3. 使用java注解处理器处理不了kotlin相关的代码,可以使用kapt参考文章

http://chatgpt.dhexx.cn/article/dY37oqan.shtml

相关文章

Annotation Processor

annotationProcessor和android-apt的功能是一样的&#xff0c;它们是替代关系。annotationProcessor是APT工具中的一种&#xff0c;他是google开发的内置框架&#xff0c;不需要引入&#xff0c;可以直接在build.gradle文件中使用。android-apt是由一位开发者自己开发的apt框架…

annotation是什么,用处,举例

1.概念&#xff1a;注解Annotation是java 1.5的新特性&#xff0c;是一种能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解&#xff0c;可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”。 2.用处&#xff1a; (1)生成文档。这…

Web大学生网页作业成品:个人博客主页 (纯HTML+CSS代码)

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 个人博客网站 | 个人主页介绍 | 个人简介 | 个人博客设计制作 | 等网站的设计与制作 | 大学生个人HTML网页设计作品 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&…

vue搭建博客

使用vue2.0/3.0搭建博客&#xff0c;前端情绪员 实现原理安装nodejs和vue写项目项目打包和托管项目创建github仓库安装git托管项目到github仓库检查仓库并设置pages购买域名和解析域名修改更新项目ps(gitee搭建vue博客) 实现原理 使用vue打包出来的dist&#xff0c;把dist中的…

博客中动态图的制作

在博客中的动态图片的制作&#xff0c;可以在Android Studio里面连上手机进行视频的录制。之后用在视频的基础上用gifcam软件转化成gif动态图&#xff1a; 下载gitcam绿色版软件进行视频的处理&#xff1a; 打开GIFcam转化成gif格式的动态图&#xff1a;

手把手,从零开始搭建个人博客网站(附源码)

项目介绍 一、项目功能&#xff1a; 实现登录功能 密码数据加密 用户管理功能&#xff08;用户增删改查&#xff09; 文章管理功能&#xff08;文章增删改查&#xff09; 文章首页展示 文章评论功能&#xff08;需登录&#xff09; 首页文章展示、 用户管理、文章管理的…

HTML5+CSS编写个人博客界面

刚入门html和css&#xff0c;花了一天入门基础&#xff0c;第二天花了五六个小时完成老师布置的作业——个人博客界面&#xff0c;初学者可以进行参考编写。 注&#xff1a;里面的链接都不能点击 废话少说&#xff0c;看结果&#xff1a; 参考代码&#xff1a; microbloy.htm…

js个人博客设计大作业

视频演示&#xff1a; 大作业演示 图片看效果&#xff1a; 网站规划设计 1.结构设计 共设计3个HTML界面&#xff1a;一个登陆界面&#xff0c;一个注册界面&#xff0c;以及主页面&#xff1b; 2.内容规划 登陆界面包含logo&#xff0c;输入用户名&#xff0c;输入密码和登…

python数据可视化:使用dash给博客制作一个dashboard

项目部署在&#xff1a;https://ffzs-blog-dashboard.herokuapp.com/ 项目代码在&#xff1a;https://github.com/ffzs/dash_blog_dashboard 1.dashboard 仪表板通常提供与特定目标或业务流程相关的关键绩效指标&#xff08;KPI&#xff09;的概览。另一方面&#xff0c;“仪表…

四、登录注册页功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 首先打开在线编辑器进入我们的项目&#xff1a;https://editor.ivx.cn/ 一、登录页功能实现 上一节中已经完成了登录页的页面制作&#xff0c;那么这一节就开始对应的完成登录页的功能实现。 登录页的功能实现主要是对用户…

七、文章管理页面及功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、文章管理页页面制作 文章管理页的基本结构与首页类似&#xff0c;我们复制一个首页&#xff0c;并且重命名首页的名称为文章管理页&#xff1a; 我们接着删除如下图所框选部分内容&#xff1a; 接着重命名导航为内容…

五、文章详情页制作及跳转功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、详情页制作 在之前的章节中&#xff0c;我们已经制作完毕了登录、注册、首页等内容&#xff0c;在这一节中&#xff0c;我们编写详情页以及详情页功能制作。 详情页页面如下&#xff1a; 详情页头部也就是一个头部栏&…

三、登录页制作《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、登录页实现 本节需要做的登录页如下&#xff1a; 该页面我们复习可以的值&#xff0c;首先设置整个页面页面的垂直和水平对其为居中&#xff0c;随后一个容器包裹对应的登录区域&#xff0c;此时我们创建一个页面命名为…

一、首页、详情页、文章编辑页制作《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、首页制作 首页预览如下&#xff1a; 首先在博客页创建一个相对应项目&#xff1a; 接着选择前台&#xff0c;创建一个页面&#xff0c;命名为首页&#xff1a; 接着更改当前屏幕为小屏尺寸&#xff1a; 接着我们分…

二、文章发布页制作及后台实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、文章编辑页制作 当首页制作完毕后&#xff0c;需要显示内容就需要有文章数据&#xff0c;此时我们创建一个文章编辑页增加对应的数据。 那么我们创建一个页面&#xff0c;命名为文章发布页&#xff1a; 接着我们查看标…

二、博客首页完成《iVX低代码仿CSDN个人博客制作》

制作iVX 低代码项目需要进入在线IDE&#xff1a;https://editor.ivx.cn/ 一、菜单思路参考及制作 在 CSDN 首页中的菜单部分为一串横排的内容&#xff0c;并且可以进行拖动&#xff1a; 首先咱们添加一个行&#xff0c;命名为菜单&#xff1a; 接着肯定是需要设置上下的内边…

四、博客详情页完成《iVX低代码仿CSDN个人博客制作》

制作iVX 低代码项目需要进入在线IDE&#xff1a;https://editor.ivx.cn/ 一、博客详情页分析 博客详情页大体分为顶部标题、发布时间、作者信息、博文内容&#xff0c;底部的评论我们在此不必做悬浮内容&#xff0c;咱们直接放到博文之下进行显示即可&#xff1b;顶部标题需要…

博客中GIF动画超简单制作

前言: 在写博客的时候图片和视频是非常通俗易懂的,比文字表述更为清晰,有时候演示过程用动画效果更好,毕竟图片只是静态的,看不出整个过程。而录制成视频,体积大,保存发送也比较麻烦,最重要的事博客中不支持这种格式,所以制作成GIF动画,方便、占用空间又小、博客也支…

一、博客首页搭建搭建《iVX低代码仿CSDN个人博客制作》

制作iVX 低代码项目需要进入在线IDE&#xff1a;https://editor.ivx.cn/ 一、头部导航栏思路参考 首先我们可以查看CSDN的博客首页&#xff0c;从中查看一下布局&#xff1a; 在以上首页中&#xff0c;我们可以得知其顶部为一个整行&#xff0c;这个行内容左侧为一个logo&am…

《博客页面制作》教程

《博客页面制作》教程 一、《博客页面制作》第一部分 效果图&#xff1a; 参考代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>博客制作-第一部分</title><style type"text/css"&g…