赞
踩
简介
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 的结构。
ARouter.png
Butterknife.png
接下来我们一步一步实现自己的注解处理器。
定义 Annotation
这里仿写 ARouter 的 @Autowired 注解。
新建一个 java library module,并定义自己的 @Autowired 注解。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Autowired {
String name();
String desc() default "";
}
它的结构如下所示。
annotation.png
定义 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
// 方法
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));
// 类名
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 的全路径写入。
它的结构大致是这样子的。
注册Processor.png
自动注册
google 提供了 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
public class PPdaiHelper {
public static void inject(Object target) {
String className = target.getClass().getName() + "
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
@Override
public void inject(Object target) {
Main2Activity inject = (Main2Activity) target;
inject.id = inject.getIntent().getLongExtra("id", inject.id);
}
}
运行代码,打开 Logcat,点击跳转,查看日志如下。
验证.png
传送门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。