APT是什么?
APT即为Annotation Processing Tool:注解处理器,它是javac的一个工具,APT可以用来在编译时扫描和处理注解。
现在很多主流的三方库都使用到了该技术,比如说ARouter、ButterKnife等。 本文主要是记录下APT的使用流程。其中很多细节已经一些坑。
1.自定义注解
先创建一个java module,专门定义注解相关逻辑:
在新生成的lib中自定义一个注解:FRC_TEST
/**
* @author: frc
* @description: 测试注解
* @date: 2021/2/2 10:18 PM
*/
@Target(ElementType.TYPE)//注解使用位置,放在类上
@Retention(RetentionPolicy.CLASS)//注解有效时间
public @interface FRC_TEST {
}
1.1 元注解
元注解是可以用在注解上的注解
元注解:@Target用来标注当前自定义注解的使用位置,有如下几个位置,比如类上、方法上、参数上等。
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
元注解:Retention用来标记当前注解有效期间,比如说运行时,编译时等。
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
这里的SOURCE和CLASS,经常让人觉得混淆。下面是个人理解:SOURCE在编译时会被移除,用来在源码阶段给开发者提示作用,比如说@Override。而CLASS是默认状态,在编译时是存在的,在运行时会被移除,它想表达的东西不会被虚拟机识别,也就是它不能被运行时反射。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
所以上面定义的FRC_TEST是 可用于类或接口上的编译时注解
2. 编译时获取注解
上面自定义的FRC_TEST注解,需要在编译时被拿到。所以在编译时要读取到该注解。如何读取呢?
javac 再将.java文件编译成.class文件之前会显扫描所有的.java文件,我们可以通过注册一个服务来监听当前扫描行为。如果检测到扫描行为,就可以过滤出被
FRC_TEST注释的文件。拿到这些文件后,在真正编译前生成一些辅助的.java文件。一起编译到.class中。
我们可以再创建一个 Java module叫frc-compiler,专门用来处理编译期间的注解相关逻辑。
2.1 注解处理类
首先我们需要定义一个类继承AbstractProcessor,这个类会在编译时被触发,用来处理自定义的注解想做的事。我们称为 注解处理器类:
package com.rong.cheng.frc_compiler;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
/**
* @author: frc
* @description: FRC_TEST注解的注解处理器
* @date: 2021/2/2 10:50 PM
*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持到的java版本
@SupportedAnnotationTypes("com.rong.cheng.frc_annotation.FRC_TEST")//支持的注解是哪个
class FrcTestAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
-
继承
AbstractProcessor -
@SupportedSourceVersion()指定支持的最大的java版本 -
@SupportedAnnotationTypes()指定当前注解处理器要处理那些注解,javac只有在扫描到指定的注解时才会回调给当前处理器。
@SupportedAnnotationTypes支持传入多个注解如下,所以一个注解处理器是支持处理多个注解的:
@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface SupportedAnnotationTypes {
/**
* Returns the names of the supported annotation types.
* @return the names of the supported annotation types
*/
String [] value();
}
上面定义了要在编译时扫描哪些注解,那么如何让当前的注解处理器能在编译时被通知到呢?需要将当前类注册成服务。
2.2 注册服务
这个需要用到google的 AutoService
在frc-compiler的build.gradle中添加如下依赖
dependencies {
//个人项目中如果只是annotationProcessor,是无法获取到AutoService,
compileOnly("com.google.auto.service:auto-service:1.0-rc7")
annotationProcessor("com.google.auto.service:auto-service:1.0-rc7"
//依赖注解工程
implementation project(":frc-annotation")
}
然后在FrcTestAnnotationProcessor上添加@AutoService注解
@AutoService(Processor.class)//注册成服务
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持到的java版本
@SupportedAnnotationTypes({"com.rong.cheng.frc_annotation.FRC_TEST"})//支持的注解是哪个
class FrcTestAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
我们通过Messager打印信息来验证是否成功:
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.rong.cheng.frc_annotation.FRC_TEST"})//支持的注解是哪个
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持到的java版本
class FrcTestAnnotationProcessor extends AbstractProcessor {
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager=processingEnv.getMessager();
//注意不能使用Diagnostic.Kind.ERROR,不然直接异常
messager.printMessage(Diagnostic.Kind.NOTE,"--------->FrcTestAnnotationProcessor init");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE,"--------->FrcTestAnnotationProcessor process");
return false;
}
}
然后再主工程中依赖注解处理器:
dependencies {
//依赖自定义的注解
implementation project(":frc-annotation")
//使用自定义的注解处理器
annotationProcessor project(":frc-compiler")
}
需要注意如果该注解是在kotlin代码中被使用需要使用kapt来依赖注解
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'//需要加kapt插件,同时这个插件必须在'kotlin-android'下面
}
dependencies {
//依赖自定义的注解
implementation project(":frc-annotation")
//使用自定义的注解处理器
kapt project(":frc-compiler")
}
我们可以通过手动查看下面文件有没有,判断注解处理器是否生效:
上面代码其实还有两个问题:
由于之前定义注解处理器类的时候忘记加 public修饰,导致编译时找不到FrcTestAnnotationProcessor类:
所以FrcTestAnnotationProcessor一定要加public修饰
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.rong.cheng.frc_annotation.FRC_TEST"})//支持的注解是哪个
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持到的java版本
public class FrcTestAnnotationProcessor extends AbstractProcessor {
}
第二个问题是 只有init中的日志输出了:
这是因为我们的FRC_TEST注解并没有真的被使用,不会执行到process方法。
下面再主app中随便添加下:
@FRC_TEST
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
这次process方法被执行了
发现两个问题:
- 日志不会换行
- process方法触发了两次
至此 在编译时读取注解就完成了。
note:如果编译没反应,可以先尝试clean项目,再make
2.3 读取Android项目传入的参数
现在都是多个module协作开发,有时注解需要根据不同module的情况做差异化处理,这时我们就要读取module传入的配置参数。
2.3.1 配置参数
build.gradle配置文件中提供了注解处理时参数设置功能:
android {
compileSdkVersion 30
buildToolsVersion "30.0.0"
defaultConfig {
applicationId "com.rong.cheng.study"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions{
annotationProcessorOptions{
arguments = ["moduleName":"app"]
}
}
}
}
javaCompileOptions:java编译时的配置选项
annotationProcessorOptions:注解处理的配置选项
arguments:配置的参数,key:value。
所以可以理解为:在java编译时配置注解处理的配置。
2.3.2 读取参数
- 首先需要在注解处理器类上加
@SupportedOptions注解 - 注解处理器init时,通过
ProcessingEnvironment获取
代码如下:
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.rong.cheng.frc_annotation.FRC_TEST"})//支持的注解是哪个
@SupportedSourceVersion(SourceVersion.RELEASE_8)//支持到的java版本
@SupportedOptions("moduleName")//读取使用该注解处理器的项目中传入的参数
public class FrcTestAnnotationProcessor extends AbstractProcessor {
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "--------->FrcTestAnnotationProcessor init \n");
String projectName = processingEnv.getOptions().get("moduleName");
messager.printMessage(Diagnostic.Kind.NOTE, "--------->module name is :" + projectName);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "--------->FrcTestAnnotationProcessor process \n");
return true;
}
}
编译一下看下输出:
2.3 读取注解修饰对象的信息
我们之前是用FRC_TEST注解修饰了MainActivity类。现在我们要在注解处理器中读到该类当前信息,这样才能做相应操作。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "--------->FrcTestAnnotationProcessor process \n");
//获取被FRC_TEST注解的类的相关信息
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(FRC_TEST.class);
//由于同一注解可能会被多地使用,所以这里要遍历下
for (Element element : elements) {
//此处通过这个Element对象就能获取到个种 被注解类的信息
//获取被注解的类名
messager.printMessage(Diagnostic.Kind.NOTE, "--------->class name \n"+element.getSimpleName());
}
return true;
}
3. 生成辅助类
生成辅助类的本质,就是构建一个符合javalei的文件格式,然后写入本地。最直接的方式就是一行一行写,先写包名,再写导入的类,然后类名,方法等逐个往文件中写。比如EventBus框架:
我们也可以借助工具来帮我们生成,减少代码量。
JavaPoet就是这样的工具:
frc-compiler module中添加javapoet依赖
dependencies {
...
//javapoet
compileOnly("com.squareup:javapoet:1.13.0")
...
}
参考JavaPoet中的demo,我们生成一个HelloWorld类,里面打印个"Hello, JavaPoet!"。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE, "--------->FrcTestAnnotationProcessor process \n");
//获取被FRC_TEST注解的类的相关信息
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(FRC_TEST.class);
for (Element element : elements) {
/**
* package com.example.helloworld;
*
* public final class HelloWorld {
* public static void main(String[] args) {
* System.out.println("Hello, JavaPoet!");
* }
* }
*/
//生成一个main方法
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, JavaPoet!")
.build();
messager.printMessage(Diagnostic.Kind.NOTE, "--------->element name \n"+element.getSimpleName());
// //生成类
TypeSpec helloWorldClass = TypeSpec.classBuilder("HelloWorld")
.addMethod(mainMethod)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.build();
//写入包信息, 整个java文件信息完成
JavaFile javaFile = JavaFile.builder("com.rong.cheng.study", helloWorldClass).build();
//将javaFile 生成文件写入本地。此处需要用到 Filer,mFiler = processingEnv.getFiler();在init中获取
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.NOTE, "生成FRC_TEST注解处理器,生成HelloWorld文件失败: " + e.getMessage());
e.printStackTrace();
}
}
编译后就会在下面位置生成如下文件:
如果是使用的kapt来依赖注解处理器会在下面位置生成文件:

生成后,就可以在源码中使用了:
至此整APT的正常使用流程就通了。
本文只是对APT的使用方式进行了简单的总结,方便自己以后查阅的同时分享给大家。对于APT的具体实际使用建议参考ARouter。