注解处理器APT的使用

简介

APT 全称 Annotation Processing Tool,即注解处理器。更确切的说,它是 javac 的一部分,能够在编译期扫描和处理注解,并生成文件。

那么使用 APT 有什么好处呢?

  1. 将一些通用的重复的代码通过 APT 生成,减少开发工作量,提高开发效率;
  2. 在不考虑编译期耗时的情况下,相较于在运行期通过反射处理的方式,更能提高程序运行效率。

现在很多著名的三方库都使用了 APT 技术,比如 butterknifeARouterdagger 等。

要使用 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 annotations,
                                    RoundEnvironment roundEnv);

    protected synchronized boolean isInitialized() {
        return initialized;
    }

}

有四个重要的方法。

  1. init() 初始化方法;
  2. process() 注解处理器处理注解和生成文件的地方,一般逻辑都会写在这里;
  3. getSupportedAnnotationTypes() 返回当前注解处理器能够处理的注解信息;
  4. getSupportedSourceVersion() 返回当前注解处理器支持的版本,没有特殊要求,一般都会使用 SourceVersion.latestSupported()

ProcessingEnvironment

它是一个接口,通过它可以获取到配置信息和一些常用的工具类。

public interface ProcessingEnvironment {
    // 获取配置信息
    Map getOptions();

    // 打印日志的工具类,也可以用 System.out.println()
    Messager getMessager();

    // 创建文件的工具类
    Filer getFiler();

    // Element相关的工具类
    Elements getElementUtils();

    // Type相关的工具类
    Types getTypeUtils();

    // 获取源码版本
    SourceVersion getSourceVersion();

}
  1. getOptions() 可以获取到配置信息,比如 ARouterbuild.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));
}
  1. getMessager() 可以获取到日志工具类,通过 Messager 可以在打印一些日志信息,当然你也可以直接使用 System.out.println() 来输出日志;
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,  "日志信息");
  1. getFiler() 可以获取到 Filer 工具类,用来创建源文件,字节码文件等;
  2. getElementUtils() 可以获取到 Elements 工具类,这是一个比较有用的工具类;
// 获取包元素
PackageElement getPackageElement(CharSequence name);
PackageElement getPackageOf(Element type);
// 是否过时
boolean isDeprecated(Element e);
// 根据全路径获取到某个类的 TypeElement 元素,这是非常有用的
TypeElement getTypeElement(CharSequence name);
...
  1. 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() {
    }
}
  1. TypeElement 一个类或接口的元素,如果注解处理器处理的对象是类或者接口,那么这个元素将被封装为 TypeElemnet
  2. Packagelement 表示包元素;
  3. VariableElement 表示变量、枚举、方法参数;
  4. ExecutableElement 表示构造函数、方法;
  5. TypeParameterElement 泛型元素。

实践

一般注解处理器都会由三个部分组成,compileannotationapi

  1. compile 一般编写注解处理器相关;
  2. annotation 一般编写一些注解和一些基础类,接口等;
  3. api 一般会编写暴露给上层业务的封装,工具等。

比如ARouterButterKnife 的结构。

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 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 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 是类对应的 TypeElementvalue 是当前类里面所有使用了 @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

这里提供两种注册方式,手动注册和自动注册。

手动注册

  1. src/main 下新建一个 resources 的目录;
  2. resources 下新建一个 META-INF 的目录;
  3. META-INF 下新建一个 services 的目录;
  4. 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$$ARouter$$Autowired.javainject()

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

验证.png

传送门

【Github 项目地址】
【ARouter 之@Autowired源码分析】

你可能感兴趣的:(注解处理器APT的使用)