天天看点

实现一个简单的Java编译时注解处理器

java注解又称java标注,是java语言5.0版本开始支持加入源代码的特殊语法元数据。

java语言中的类、方法、变量、参数和包等都可以被标注。java标注和javadoc不同,标注有自反性。在编译器生成类文件时,标注可以被嵌入到字节码中,由java虚拟机执行时获取到标注。

根据元注解<code>@retention</code>指定值的不同,注解可分为<code>source</code>、<code>class</code>和<code>runtime</code>三种类型。当被声明为source时,注解仅仅在源码级别被保留,编译时被丢弃;声明为class时,注解会由编译器记录在class文件内,但在运行时会被忽略,默认的retention级别即为class;声明为runtime时,注解将被保留到运行时,可通过反射在运行时获取到。

下面我们针对class级别的注解,介绍在编译期处理注解的方法。

举个栗子,此处我们定义一个用于标注field的注解meta,包含两个参数repeat和id,在编译阶段我们将通过处理这一注解,给被标注的field赋值,如repeat为2,id为aa,则被标注的field会被赋值为"aaaa"。

在field上使用注解

下面我们基于android studio编写一个处理上文中定义的<code>meta</code>注解的处理器。

此处我们将注解解析器作为android project中的一个module来开发,新建一个module,类型选择java library。

注解需要通过注解处理器进行处理,所有的注解处理器都实现了<code>processor</code>接口,一般我们选择继承<code>abstractprocessor</code>来创建自定义注解处理器。

继承<code>abstractprocessor</code>,实现<code>public boolean process(set&lt;? extends typeelement&gt; annotations, roundenvironment roundenv)</code>方法。方法参数中annotations包含了该处理器声明支持并已经在源码中使用了的注解,roundenv则包含了注解处理的上下文环境。 此方法返回true时,表示此注解已经被处理完毕,返回false时将会交给其他处理器继续处理。

覆盖<code>getsupportedsourceversion</code>方法,返回处理器支持的源码版本,一般直接返回<code>sourceversion.latestsupported()</code>即可。

覆盖<code>getsupportedannotationtypes</code>方法,返回处理器想要处理的注解类型,此处需返回一个包含了所有注解完全限定名的集合。

在java 7及以上,可以使用类注解<code>@supportedannotationtypes</code>和<code>@supportedsourceversion</code>替代上面的方法进行声明。

注解处理器在使用前需要先向jvm注册,在module的meta-inf目录下新建services目录,并创建一个名为<code>javax.annotation.processing.processor</code>的文件,在此文件内逐行声明注解处理器。同样地,此处需要声明的也是处理器类的完全限定名。

另一个简便的方法是使用google提供的auto-services库,在build.gradle中引入<code>com.google.auto.service:auto-service:1.0-rc2</code>,并在处理器类上添加注解<code>@autoservice(processor.class)</code>,auto-services也是一个注解处理器,会在编译时为该module生成声明文件。

首选我们定义一个接口来规范生成的类:

再定义一个类结构来描述我们生成的java类:

如果我们有一个类a,其中的field f包含了meta注解,我们会为其生成一个agen类,并在action方法中完成对f的赋值操作。

在process方法中完成对注解的解析和代码生成操作:

在运行时可以使用反射来访问生成的类,此处定义了一个简单的帮助类来实例化生成的类并给目标field赋值:

在目标类初始化的时候调用<code>metaloader.load</code>,传入目标类的实例,便可完成对field的赋值操作。

由于在前面引入了<code>auto-service</code>库,最终打包apk的时候会报错<code>duplicate files copied in apk meta-inf/services/javax.annotation.processing.processor</code>,而该文件在运行时又是不需要的,所以可以在packagingoptions中排除这个文件以规避该错误:

然而这并不是彻底的解决方案,如上所述,注解处理器在运行时是完全无用的,能否让其仅存在于编译期而不打包进最终产物内呢?答案是肯定的。

在工程的build.gradle内添加插件:

在module的build.gradle内应用插件:

应用插件后,dependencies会新增一个新的依赖方法<code>apt</code>,修改依赖声明为:

如此声明后处理器module内的类将不会被打包到最终的产物中,有利于缩小产物体积。

在android studio中添加新的run/debug configurations,类型选择remote;

在工程的gradle.properties中添加

选中上面定义的configuration,点击debug按钮等待目标进程attach;

在注解处理器逻辑内设置断点,选择rebuild project,触发注解处理器处理逻辑即可实现断点调试。