当前位置:   article > 正文

apt java or_APT 的使用

private boolean initialized = false;

简介

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 的结构。

5c6ec7e5daa9

ARouter.png

5c6ec7e5daa9

Butterknife.png

接下来我们一步一步实现自己的注解处理器。

定义 Annotation

这里仿写 ARouter 的 @Autowired 注解。

新建一个 java library module,并定义自己的 @Autowired 注解。

@Retention(RetentionPolicy.SOURCE)

@Target(ElementType.FIELD)

public @interface Autowired {

String name();

String desc() default "";

}

它的结构如下所示。

5c6ec7e5daa9

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

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 的全路径写入。

它的结构大致是这样子的。

5c6ec7e5daa9

注册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

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,点击跳转,查看日志如下。

5c6ec7e5daa9

验证.png

传送门

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/240155
推荐阅读
相关标签
  

闽ICP备14008679号