简介
APT
全称Annotation Processing Tool
,即注解处理器。更确切的说,它是javac
的一部分,能够在编译期扫描和处理注解,并生成文件。
那么使用 APT
有什么好处呢?
- 将一些通用的重复的代码通过
APT
生成,减少开发工作量,提高开发效率; - 在不考虑编译期耗时的情况下,相较于在运行期通过反射处理的方式,更能提高程序运行效率。
现在很多著名的三方库都使用了 APT
技术,比如 butterknife
,ARouter
,dagger
等。
要使用
APT
,首先得了解AbstractProcessor.java
,所有自定义的注解处理器都需要继承这个类。
public abstract class AbstractProcessor implements Processor {
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
public Set getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " +
"found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
public abstract boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv);
protected synchronized boolean isInitialized() {
return initialized;
}
}
有四个重要的方法。
-
init()
初始化方法; -
process()
注解处理器处理注解和生成文件的地方,一般逻辑都会写在这里; -
getSupportedAnnotationTypes()
返回当前注解处理器能够处理的注解信息; -
getSupportedSourceVersion()
返回当前注解处理器支持的版本,没有特殊要求,一般都会使用SourceVersion.latestSupported()
。
ProcessingEnvironment
它是一个接口,通过它可以获取到配置信息和一些常用的工具类。
public interface ProcessingEnvironment {
// 获取配置信息
Map getOptions();
// 打印日志的工具类,也可以用 System.out.println()
Messager getMessager();
// 创建文件的工具类
Filer getFiler();
// Element相关的工具类
Elements getElementUtils();
// Type相关的工具类
Types getTypeUtils();
// 获取源码版本
SourceVersion getSourceVersion();
}
-
getOptions()
可以获取到配置信息,比如ARouter
在build.gradle
文件中配置的AROUTER_MODULE_NAME
信息;
Map options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
// KEY_MODULE_NAME 就是 "AROUTER_MODULE_NAME"
moduleName = options.get(KEY_MODULE_NAME);
generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
}
-
getMessager()
可以获取到日志工具类,通过Messager
可以在打印一些日志信息,当然你也可以直接使用System.out.println()
来输出日志;
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "日志信息");
-
getFiler()
可以获取到Filer
工具类,用来创建源文件,字节码文件等; -
getElementUtils()
可以获取到Elements
工具类,这是一个比较有用的工具类;
// 获取包元素
PackageElement getPackageElement(CharSequence name);
PackageElement getPackageOf(Element type);
// 是否过时
boolean isDeprecated(Element e);
// 根据全路径获取到某个类的 TypeElement 元素,这是非常有用的
TypeElement getTypeElement(CharSequence name);
...
-
getTypeUtils()
获取到Types
工具类,这是一个比较有用的工具类;
// t1 是否是 t2 的子类
boolean isSubtype(TypeMirror t1, TypeMirror t2);
...
Element
Element
是注解处理器中比较重要存在。所有经过注解处理器扫描后的元素都会被封装成Element
。
Element
是一个接口,有五个实现类,分别代表了不同类型的元素,举个栗子。
// 1. 包,被封装为 PackageElement
package com.ppdai;
/*
* 2. 类,被封装为 TypeElement
* 3. 泛型,被封装为 TypeParameterElement
*/
public class Example {
// 4. 变量,被分装为 VariableElement
private int a;
// 5. 方法,被封装为 ExecutableElement
public void b() {
}
}
-
TypeElement
一个类或接口的元素,如果注解处理器处理的对象是类或者接口,那么这个元素将被封装为TypeElemnet
; -
Packagelement
表示包元素; -
VariableElement
表示变量、枚举、方法参数; -
ExecutableElement
表示构造函数、方法; -
TypeParameterElement
泛型元素。
实践
一般注解处理器都会由三个部分组成,compile
,annotation
,api
。
-
compile
一般编写注解处理器相关; -
annotation
一般编写一些注解和一些基础类,接口等; -
api
一般会编写暴露给上层业务的封装,工具等。
比如ARouter
,ButterKnife
的结构。
接下来我们一步一步实现自己的注解处理器。
定义 Annotation
这里仿写
ARouter
的@Autowired
注解。
新建一个 java library module,并定义自己的 @Autowired
注解。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Autowired {
String name();
String desc() default "";
}
它的结构如下所示。
定义 Annotation Processor
想要在编译期对注解进行处理,并生成对应的文件,需要实现
AbstractProcessor
。
新建一个 java library module,定义 AutowiredProcessor
继承自 AbstractProcessor
,并重写 getSupportedAnnotationTypes()
添加对 @Autowired
注解的支持。
public class AutowiredProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set getSupportedAnnotationTypes() {
HashSet set = new HashSet<>();
set.add(Autowired.class.getCanonicalName());
return set;
}
}
因为每个类都可以有多个使用 @Autowired
注解的属性,这里新增一个 categories()
先对所有使用了 @Autowired
的属性进行分类,存放到 map
里面。
// 用来存放分类后的数据
private HashMap> map = new HashMap<>();
private void categories(Set extends Element> set) {
for (Element element : set) {
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
if (map.containsKey(typeElement)) {
map.get(typeElement).add(element);
} else {
List list = new ArrayList<>();
list.add(element);
map.put(typeElement, list);
}
}
}
分类后 key
是类对应的 TypeElement
,value
是当前类里面所有使用了 @Autowired
注解的属性对应的 Element
。
然后新增一个 generate()
用来处理分类后的数据,并生成对应的文件。简单起见,这里只对 Activity
里面的逻辑进行了处理。
private void generateFile() {
for (Map.Entry> entry : map.entrySet()) {
TypeElement typeElement = entry.getKey();
List elementList = entry.getValue();
PackageElement packageElement = elementUtils.getPackageOf(typeElement);
// 获取包名
String packageName = packageElement.getQualifiedName().toString();
String sourceClassName = typeElement.getSimpleName().toString();
// 定义生成的文件名
String genClassName = String.format("%s$$ARouter$$Autowired", sourceClassName);
// 方法
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(Object.class, "target")
.addStatement("$T inject = ($T) target", ClassName.get(typeElement), ClassName.get(typeElement));
// 类名$$ARouter$$Autowired
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(genClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ISyringe.class);
TypeMirror activity = elementUtils.getTypeElement("android.app.Activity").asType();
for (Element element : elementList) {
Autowired autowired = element.getAnnotation(Autowired.class);
String key = autowired.name();
TypeMirror typeMirror = element.asType();
String fieldName = element.getSimpleName().toString();
if (typeUtils.isSubtype(typeElement.asType(), activity)) {
String source = "inject.getIntent()";
switch (typeMirror.getKind().toString()) {
case "BOOLEAN":
methodBuilder.addStatement("inject.$L = $L.getBooleanExtra($S, inject.$L)", fieldName, source, key, fieldName);
break;
case "LONG":
methodBuilder.addStatement("inject.$L = $L.getLongExtra($S, inject.$L)", fieldName, source, key, fieldName);
break;
// ...
default:
}
}
}
try {
JavaFile.builder(packageName, typeBuilder.addMethod(methodBuilder.build()).build()).build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
注册 Annotation Processor
这里提供两种注册方式,手动注册和自动注册。
手动注册
- 在
src/main
下新建一个resources
的目录; - 在
resources
下新建一个META-INF
的目录; - 在
META-INF
下新建一个services
的目录; - 在
services
下新建一个javax.annotation.processing.Processor
的文件,并将要注册的Annotation Processor
的全路径写入。
它的结构大致是这样子的。
自动注册
auto-service
库来简化注册过程。
修改 build.gradle
文件,添加依赖关系。
implementation 'com.google.auto.service:auto-service-annotations:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
修改 AutowiredProcessor
,在类上添加注解 @AutoService
即可。
@AutoService(Processor.class)
public class AutowiredProcessor extends AbstractProcessor {
// ...
}
其实 @AutoService
也是通过 Annotation Processor
来实现的。具体实现我们可以查看 AutoServiceProcessor.java
文件,它的调用链如下 process() -> processImpl() -> generateConfigFiles()
,当 @AutoService
注解处理完的时候,会调用 generateConfigFiles()
, 我们可以看看 generateConfigFiles()
方法的具体实现。
private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();
for (String providerInterface : providers.keySet()) {
// 熟悉吧,这里就是我们前面创建的 src/main/resources/META-INF/services/javax.annotation.processing.Processor
String resourceFile = "META-INF/services/" + providerInterface;
log("Working on resource file: " + resourceFile);
try {
SortedSet allServices = Sets.newTreeSet();
try {
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
log("Looking for existing resource file at " + existingFile.toUri());
Set oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
log("Existing service entries: " + oldServices);
allServices.addAll(oldServices);
} catch (IOException e) {
log("Resource file did not already exist.");
}
Set newServices = new HashSet(providers.get(providerInterface));
if (allServices.containsAll(newServices)) {
log("No new service entries being added.");
return;
}
allServices.addAll(newServices);
log("New service file contents: " + allServices);
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
log("Wrote to: " + fileObject.toUri());
} catch (IOException e) {
fatalError("Unable to create " + resourceFile + ", " + e);
return;
}
}
}
定义 API
创建一个 android library module,定义一个工具类,来调用生成 XX$$ARouter$$Autowired.java
的 inject()
。
public class PPdaiHelper {
public static void inject(Object target) {
String className = target.getClass().getName() + "$$ARouter$$Autowired";
try {
ISyringe iSyringe = (ISyringe) Class.forName(className).getConstructor().newInstance();
iSyringe.inject(target);
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改 app
下面的 build.gradle
文件,增加对 Annotation Processor
的使用。
dependencies {
//...
implementation project(":ppdai-api")
kapt project(":ppdai-compile")
}
做完这些,自定义的 Annotation Processor
就完成了。接下来就是验证了。简单起见,我们编写了两个 Activity
,其中一个使用 @Autowired
注解进行数据传递。
class Main2Activity : AppCompatActivity() {
@Autowired(name = "id")
@JvmField
var id: Long = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
PPdaiHelper.inject(this)
Log.d("ppdai", "id : $id")
}
}
跳转 Main2Activity.java
的地方增加参数 id
的传递。
val intent = Intent(this, Main2Activity::class.java)
intent.putExtra("id", 1000L)
startActivity(intent)
然后编译项目,看看我们的注解处理器生成的文件。
package com.ppdai.annotationprocessor;
import com.ppdai.core.ISyringe;
import java.lang.Object;
import java.lang.Override;
public class Main2Activity$$ARouter$$Autowired implements ISyringe {
@Override
public void inject(Object target) {
Main2Activity inject = (Main2Activity) target;
inject.id = inject.getIntent().getLongExtra("id", inject.id);
}
}
运行代码,打开 Logcat
,点击跳转,查看日志如下。
传送门
【Github 项目地址】
【ARouter 之@Autowired源码分析】