注解處理器APT的使用

簡(jiǎn)介

APT 全稱 Annotation Processing Tool,即注解處理器。更確切的說(shuō),它是 javac 的一部分,能夠在編譯期掃描和處理注解,并生成文件。

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

  1. 將一些通用的重復(fù)的代碼通過(guò) APT 生成,減少開(kāi)發(fā)工作量,提高開(kāi)發(fā)效率;
  2. 在不考慮編譯期耗時(shí)的情況下,相較于在運(yùn)行期通過(guò)反射處理的方式,更能提高程序運(yùn)行效率。

現(xiàn)在很多著名的三方庫(kù)都使用了 APT 技術(shù),比如 butterknifeARouter,dagger 等。

要使用 APT,首先得了解 AbstractProcessor.java ,所有自定義的注解處理器都需要繼承這個(gè)類。

public abstract class AbstractProcessor implements Processor {

    protected ProcessingEnvironment processingEnv;
    private boolean initialized = false;

    public Set<String> 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;
    }

}

有四個(gè)重要的方法。

  1. init() 初始化方法;
  2. process() 注解處理器處理注解和生成文件的地方,一般邏輯都會(huì)寫在這里;
  3. getSupportedAnnotationTypes() 返回當(dāng)前注解處理器能夠處理的注解信息;
  4. getSupportedSourceVersion() 返回當(dāng)前注解處理器支持的版本,沒(méi)有特殊要求,一般都會(huì)使用 SourceVersion.latestSupported()。

ProcessingEnvironment

它是一個(gè)接口,通過(guò)它可以獲取到配置信息和一些常用的工具類。

public interface ProcessingEnvironment {
    // 獲取配置信息
    Map<String,String> getOptions();

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

    // 創(chuàng)建文件的工具類
    Filer getFiler();

    // Element相關(guān)的工具類
    Elements getElementUtils();

    // Type相關(guān)的工具類
    Types getTypeUtils();

    // 獲取源碼版本
    SourceVersion getSourceVersion();

}
  1. getOptions() 可以獲取到配置信息,比如 ARouterbuild.gradle 文件中配置的 AROUTER_MODULE_NAME 信息;
Map<String, String> 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() 可以獲取到日志工具類,通過(guò) Messager 可以在打印一些日志信息,當(dāng)然你也可以直接使用 System.out.println() 來(lái)輸出日志;
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,  "日志信息");
  1. getFiler() 可以獲取到 Filer 工具類,用來(lái)創(chuàng)建源文件,字節(jié)碼文件等;
  2. getElementUtils() 可以獲取到 Elements 工具類,這是一個(gè)比較有用的工具類;
// 獲取包元素
PackageElement getPackageElement(CharSequence name);
PackageElement getPackageOf(Element type);
// 是否過(guò)時(shí)
boolean isDeprecated(Element e);
// 根據(jù)全路徑獲取到某個(gè)類的 TypeElement 元素,這是非常有用的
TypeElement getTypeElement(CharSequence name);
...
  1. getTypeUtils() 獲取到 Types 工具類,這是一個(gè)比較有用的工具類;
// t1 是否是 t2 的子類
boolean isSubtype(TypeMirror t1, TypeMirror t2);
...

Element

Element 是注解處理器中比較重要存在。所有經(jīng)過(guò)注解處理器掃描后的元素都會(huì)被封裝成 Element

Element 是一個(gè)接口,有五個(gè)實(shí)現(xiàn)類,分別代表了不同類型的元素,舉個(gè)栗子。

// 1. 包,被封裝為 PackageElement 
package com.ppdai;

/* 
 * 2. 類,被封裝為 TypeElement
 * 3. 泛型,被封裝為 TypeParameterElement
 */
public class Example<T> {
    // 4. 變量,被分裝為 VariableElement
    private int a;

    // 5. 方法,被封裝為 ExecutableElement
    public void b() {
    }
}
  1. TypeElement 一個(gè)類或接口的元素,如果注解處理器處理的對(duì)象是類或者接口,那么這個(gè)元素將被封裝為 TypeElemnet
  2. Packagelement 表示包元素;
  3. VariableElement 表示變量、枚舉、方法參數(shù);
  4. ExecutableElement 表示構(gòu)造函數(shù)、方法;
  5. TypeParameterElement 泛型元素。

實(shí)踐

一般注解處理器都會(huì)由三個(gè)部分組成,compile,annotationapi。

  1. compile 一般編寫注解處理器相關(guān);
  2. annotation 一般編寫一些注解和一些基礎(chǔ)類,接口等;
  3. api 一般會(huì)編寫暴露給上層業(yè)務(wù)的封裝,工具等。

比如ARouter,ButterKnife 的結(jié)構(gòu)。

ARouter.png
Butterknife.png

接下來(lái)我們一步一步實(shí)現(xiàn)自己的注解處理器。

定義 Annotation

這里仿寫 ARouter@Autowired 注解。

新建一個(gè) java library module,并定義自己的 @Autowired 注解。

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Autowired {
    String name();

    String desc() default "";
}

它的結(jié)構(gòu)如下所示。

annotation.png

定義 Annotation Processor

想要在編譯期對(duì)注解進(jìn)行處理,并生成對(duì)應(yīng)的文件,需要實(shí)現(xiàn) AbstractProcessor。

新建一個(gè) java library module,定義 AutowiredProcessor 繼承自 AbstractProcessor,并重寫 getSupportedAnnotationTypes() 添加對(duì) @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<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(Autowired.class.getCanonicalName());
        return set;
    }

}

因?yàn)槊總€(gè)類都可以有多個(gè)使用 @Autowired 注解的屬性,這里新增一個(gè) categories() 先對(duì)所有使用了 @Autowired 的屬性進(jìn)行分類,存放到 map 里面。

// 用來(lái)存放分類后的數(shù)據(jù)
private HashMap<TypeElement, List<Element>> 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<Element> list = new ArrayList<>();
            list.add(element);
            map.put(typeElement, list);
        }
    }
}

分類后 key 是類對(duì)應(yīng)的 TypeElementvalue 是當(dāng)前類里面所有使用了 @Autowired 注解的屬性對(duì)應(yīng)的 Element。

然后新增一個(gè) generate() 用來(lái)處理分類后的數(shù)據(jù),并生成對(duì)應(yīng)的文件。簡(jiǎn)單起見(jiàn),這里只對(duì) Activity 里面的邏輯進(jìn)行了處理。

private void generateFile() {
    for (Map.Entry<TypeElement, List<Element>> entry : map.entrySet()) {
        TypeElement typeElement = entry.getKey();
        List<Element> 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();
        }
    }
}

注冊(cè) Annotation Processor

這里提供兩種注冊(cè)方式,手動(dòng)注冊(cè)和自動(dòng)注冊(cè)。

手動(dòng)注冊(cè)

  1. src/main 下新建一個(gè) resources 的目錄;
  2. resources 下新建一個(gè) META-INF 的目錄;
  3. META-INF 下新建一個(gè) services 的目錄;
  4. services 下新建一個(gè) javax.annotation.processing.Processor 的文件,并將要注冊(cè)的 Annotation Processor 的全路徑寫入。

它的結(jié)構(gòu)大致是這樣子的。

注冊(cè)Processor.png

自動(dòng)注冊(cè)

google 提供了 auto-service 庫(kù)來(lái)簡(jiǎn)化注冊(cè)過(guò)程。

修改 build.gradle 文件,添加依賴關(guān)系。

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 {
    // ...
}

其實(shí) @AutoService 也是通過(guò) Annotation Processor 來(lái)實(shí)現(xiàn)的。具體實(shí)現(xiàn)我們可以查看 AutoServiceProcessor.java 文件,它的調(diào)用鏈如下 process() -> processImpl() -> generateConfigFiles() ,當(dāng) @AutoService 注解處理完的時(shí)候,會(huì)調(diào)用 generateConfigFiles(), 我們可以看看 generateConfigFiles() 方法的具體實(shí)現(xiàn)。

private void generateConfigFiles() {
  Filer filer = processingEnv.getFiler();

  for (String providerInterface : providers.keySet()) {
    // 熟悉吧,這里就是我們前面創(chuàng)建的 src/main/resources/META-INF/services/javax.annotation.processing.Processor
    String resourceFile = "META-INF/services/" + providerInterface;
    log("Working on resource file: " + resourceFile);
    try {
      SortedSet<String> allServices = Sets.newTreeSet();
      try {
        FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
            resourceFile);
        log("Looking for existing resource file at " + existingFile.toUri());
        Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
        log("Existing service entries: " + oldServices);
        allServices.addAll(oldServices);
      } catch (IOException e) {
        log("Resource file did not already exist.");
      }

      Set<String> newServices = new HashSet<String>(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

創(chuàng)建一個(gè) android library module,定義一個(gè)工具類,來(lái)調(diào)用生成 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 文件,增加對(duì) Annotation Processor 的使用。

dependencies {
    //...
    implementation project(":ppdai-api")
    kapt project(":ppdai-compile")
}

做完這些,自定義的 Annotation Processor 就完成了。接下來(lái)就是驗(yàn)證了。簡(jiǎn)單起見(jiàn),我們編寫了兩個(gè) Activity,其中一個(gè)使用 @Autowired 注解進(jìn)行數(shù)據(jù)傳遞。

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")
    }

}

跳轉(zhuǎn) Main2Activity.java 的地方增加參數(shù) id 的傳遞。

val intent = Intent(this, Main2Activity::class.java)
intent.putExtra("id", 1000L)
startActivity(intent)

然后編譯項(xiàng)目,看看我們的注解處理器生成的文件。

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);
  }
}

運(yùn)行代碼,打開(kāi) Logcat,點(diǎn)擊跳轉(zhuǎn),查看日志如下。

驗(yàn)證.png

傳送門

Github 項(xiàng)目地址
ARouter 之@Autowired源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容