本文同步發佈在CSDN,未經本人容許不得轉載html
上篇文章咱們使用註解+反射實現了一個仿ButterKnife功能的示例。考慮到反射是在運行時完成的,多少會影響程序性能。所以,ButterKnife自己並不是基於註解+反射來實現的,而是用APT技術在編譯時處理的。APT什麼呢?接下來一塊兒來看。java
1.什麼是APT? APT即爲Annotation Processing Tool,它是javac的一個工具,中文意思爲編譯時註解處理器。APT能夠用來在編譯時掃描和處理註解。經過APT能夠獲取到註解和被註解對象的相關信息,在拿到這些信息後咱們能夠根據需求來自動的生成一些代碼,省去了手動編寫。注意,獲取註解及生成代碼都是在代碼編譯時候完成的,相比反射在運行時處理註解大大提升了程序性能。APT的核心是AbstractProcessor類,關於AbstractProcessor類後面會作詳細說明。git
2.哪裏用到了APT?github
APT技術被普遍的運用在Java框架中,包括Android項以及Java後臺項目,除了上面咱們提到的ButterKnife以外,像EventBus 、Dagger2以及阿里的ARouter路由框架等都運用到APT技術,所以要想了解以、探究這些第三方框架的實現原理,APT就是咱們必需要掌握的。bash
3.如何在Android Studio中構建一個APT項目?app
APT項目須要由至少兩個Java Library模塊組成,不知道什麼是Java Library?不要緊,手把手來叫你如何建立一個Java Library。 框架
1.首先須要一個Annotation模塊,這個用來存放自定義的註解。ide
2.另外須要一個Compiler模塊,這個模塊依賴Annotation模塊。工具
3.項目的App模塊和其它的業務模塊都須要依賴Annotation模塊,同時須要經過annotationProcessor依賴Compiler模塊。 app模塊的gradle中依賴關係以下:性能
implementation project(':annotation')
annotationProcessor project(':factory-compiler')
複製代碼
APT項目的模塊的結構圖以下所示:
爲何要強調上述兩個模塊必定要是Java Library?若是建立Android Library模塊你會發現不能找到AbstractProcessor這個類,這是由於Android平臺是基於OpenJDK的,而OpenJDK中不包含APT的相關代碼。所以,在使用APT時,必須在Java Library中進行。
在學習Java基礎的時候想必你們都寫過簡單工廠模式的例子,回想一下什麼是簡單工廠模式。接下來引入一個工廠模式的例子,首先定義一個形狀的接口IShape,併爲其添加 draw()方法:
public interface IShape {
void draw();
}
複製代碼
接下來定義幾個形狀實現IShape接口,並重寫draw()方法:
public class Rectangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Rectangle");
}
}
public class Triangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Triangle");
}
}
public class Circle implements IShape {
@Override
public void draw() {
System.out.println("Draw a circle");
}
}
複製代碼
接下來咱們須要一個工廠類,這個類接收一個參數,根據咱們傳入的參數建立出對應的形狀,代碼以下:
public class ShapeFactory {
public Shape create(String id) {
if (id == null) {
throw new IllegalArgumentException("id is null!");
}
if ("Circle".equals(id)) {
return new Circle();
}
if ("Rectangle".equals(id)) {
return new Rectangle();
}
if ("Triangle".equals(id)) {
return new Triangle();
}
throw new IllegalArgumentException("Unknown id = " + id);
}
}
複製代碼
以上就是一個簡單工廠模式的示例代碼,想必你們都可以理解。
那麼,如今問題來了,在項目開發過程當中,咱們隨時可能會添加一個新的形狀。此時就不得不修改工廠類來適配新添加的形狀了。試想一下,每添加一個形狀類都須要咱們手動去更新Factory類,是否是影響了咱們的開發效率?若是這個Factory類可以根據咱們添加新的形狀來同步更新Factory代碼,豈不是就省了咱們不少時間了嗎?
應該怎麼作才能知足上述需求呢?在第一節中已經提到了使用APT能夠幫助咱們自動生成代碼。那麼這個工廠類是否是可使用APT技術來自動生成呢?咱們惟一要作的事情就是新添加的形狀類上加上一個註解,註解處理器就會在編譯時根據註解信息自動生成ShapeFactory類的代碼了,美哉,美哉!理想很豐滿,可是,現實很骨感。雖然已經明確了要作什麼,可是想要註解處理器幫咱們生成代碼,卻還有很長的路要走。不過,不當緊,接下來咱們將一步步實現註解處理器並讓其自動生成Factory類。
1.定義Factory註解 首先在annotation模塊下添加一個Factory的註解,Factory註解的Target爲ElementType,表示它能夠註解類、接口或者枚舉。Retention指定爲RetentionPolicy.CLASS,表示該在字節碼中有效。Factory註解添加兩個成員,一個Class類型的type,用來表示註解的類的類型,相同的類型表示屬於同一個工廠。令需一個String類型的id,用來表示註解的類的名稱。Factory註解代碼以下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
Class type();
String id();
}
複製代碼
接下來咱們用@Factory去註解形狀類,以下:
@Factory(id = "Rectangle", type = IShape.class)
public class Rectangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Rectangle");
}
}
... 其餘形狀類代碼相似再也不貼出
複製代碼
**2.認識AbstractProcessor **
接下來,就到了咱們本篇文章所要講的核心了。沒錯,就是AbstractProcessor!咱們先在factory-compiler模塊下建立一個FactoryProcessor類繼承AbstractProcessor ,並重寫相應的方法,代碼以下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
複製代碼
能夠看到,在這個類上添加了@AutoService註解,它的做用是用來生成META-INF/services/javax.annotation.processing.Processor文件的,也就是咱們在使用註解處理器的時候須要手動添加META-INF/services/javax.annotation.processing.Processor,而有了@AutoService後它會自動幫咱們生成。AutoService是Google開發的一個庫,使用時須要在factory-compiler中添加依賴,以下:
implementation 'com.google.auto.service:auto-service:1.0-rc4'
複製代碼
接下來咱們將目光移到FactoryProcessor類內部,能夠看到在這個類中重寫了四個方法,咱們由易到難依次來看:
(1) public SourceVersion getSupportedSourceVersion()
這個方法很是簡單,只有一個返回值,用來指定當前正在使用的Java版本,一般return SourceVersion.latestSupported()便可。
(2) public Set<String> getSupportedAnnotationTypes()
這個方法的返回值是一個Set集合,集合中指要處理的註解類型的名稱(這裏必須是完整的包名+類名,例如com.example.annotation.Factory)。因爲在本例中只須要處理@Factory註解,所以Set集合中只須要添加@Factory的名稱便可。
(3) public synchronized void init(ProcessingEnvironment processingEnvironment)
這個方法用於初始化處理器,方法中有一個ProcessingEnvironment類型的參數,ProcessingEnvironment是一個註解處理工具的集合。它包含了衆多工具類。例如: Filer能夠用來編寫新文件; Messager能夠用來打印錯誤信息; Elements是一個能夠處理Element的工具類。
在這裏咱們有必要認識一下什麼是Element
在Java語言中,Element是一個接口,表示一個程序元素,它能夠指代包、類、方法或者一個變量。Element已知的子接口有以下幾種:
PackageElement 表示一個包程序元素。提供對有關包及其成員的信息的訪問。 ExecutableElement 表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註釋類型元素。 TypeElement 表示一個類或接口程序元素。提供對有關類型及其成員的信息的訪問。注意,枚舉類型是一種類,而註解類型是一種接口。 VariableElement 表示一個字段、enum 常量、方法或構造方法參數、局部變量或異常參數。
接下來,我但願你們先來理解一個新的概念,即拋棄咱們現有對Java類的理解,把Java類看做是一個結構化的文件。什麼意思?就是把Java類看做一個相似XML或者JSON同樣的東西。有了這個概念以後咱們就能夠很容易的理解什麼是Element了。帶着這個概念來看下面的代碼:
package com.zhpan.mannotation.factory; // PackageElement
public class Circle { // TypeElement
private int i; // VariableElement
private Triangle triangle; // VariableElement
public Circle() {} // ExecuteableElement
public void draw( // ExecuteableElement
String s) // VariableElement
{
System.out.println(s);
}
@Override
public void draw() { // ExecuteableElement
System.out.println("Draw a circle");
}
}
複製代碼
如今明白了嗎?不一樣類型Element其實就是映射了Java中不一樣的類元素!知曉這個概念後將對理解後邊的代碼有很大的幫助。
(4) public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
終於,到了FactoryProcessor類中最後一個也是最重要的一個方法了。先看這個方法的返回值,是一個boolean類型,返回值表示註解是否由當前Processor 處理。若是返回 true,則這些註解由此註解來處理,後續其它的 Processor 無需再處理它們;若是返回 false,則這些註解未在此Processor中處理並,那麼後續 Processor 能夠繼續處理它們。 在這個方法的方法體中,咱們能夠校驗被註解的對象是否合法、能夠編寫處理註解的代碼,以及自動生成須要的java文件等。所以說這個方法是AbstractProcessor 中的最重要的一個方法。咱們要處理的大部分邏輯都是在這個方法中完成。
瞭解上述四個方法以後咱們即可以初步的來編寫FactoryProcessor類的代碼了,以下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
private Types mTypeUtils;
private Messager mMessager;
private Filer mFiler;
private Elements mElementUtils;
private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mTypeUtils = processingEnvironment.getTypeUtils();
mMessager = processingEnvironment.getMessager();
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Factory.class.getCanonicalName());
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 掃描全部被@Factory註解的元素
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
複製代碼
上述FactoryProcessor 代碼中在process方法中經過roundEnv.getElementsAnnotatedWith(Factory.class)方法已經拿到了被註解的元素的集合。正常狀況下,這個集合中應該包含的是全部被Factory註解的Shape類的元素,也就是一個TypeElement。但在編寫程序代碼時可能有新來的同事不太瞭解@Factory的用途而誤把@Factory用在接口或者抽象類上,這是不符合咱們的標準的。所以,須要在process方法中判斷被@Factory註解的元素是不是一個類,若是不是一個類元素,那麼就拋出異常,終止編譯。代碼以下:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 經過RoundEnvironment獲取到全部被@Factory註解的對象
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
if (annotatedElement.getKind() != ElementKind.CLASS) {
throw new ProcessingException(annotatedElement, "Only classes can be annotated with @%s",
Factory.class.getSimpleName());
}
TypeElement typeElement = (TypeElement) annotatedElement;
FactoryAnnotatedClass annotatedClass = new FactoryAnnotatedClass(typeElement);
...
}
return true;
}
複製代碼
基於面向對象的思想,咱們能夠將annotatedElement中包含的信息封裝成一個對象,方便後續使用,所以,另外能夠另外聲明一個FactoryAnnotatedClass來解析並存放annotatedElement的相關信息。FactoryAnnotatedClass代碼以下:
public class FactoryAnnotatedClass {
private TypeElement mAnnotatedClassElement;
private String mQualifiedSuperClassName;
private String mSimpleTypeName;
private String mId;
public FactoryAnnotatedClass(TypeElement classElement) {
this.mAnnotatedClassElement = classElement;
Factory annotation = classElement.getAnnotation(Factory.class);
mId = annotation.id();
if (mId.length() == 0) {
throw new IllegalArgumentException(
String.format("id() in @%s for class %s is null or empty! that's not allowed",
Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
}
// Get the full QualifiedTypeName
try { // 該類已經被編譯
Class<?> clazz = annotation.type();
mQualifiedSuperClassName = clazz.getCanonicalName();
mSimpleTypeName = clazz.getSimpleName();
} catch (MirroredTypeException mte) {// 該類未被編譯
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
mQualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
mSimpleTypeName = classTypeElement.getSimpleName().toString();
}
}
// ...省去getter
}
複製代碼
爲了生成合乎要求的ShapeFactory類,在生成ShapeFactory代碼前須要對被Factory註解的元素進行一系列的校驗,只有經過校驗,符合要求了才能夠生成ShapeFactory代碼。根據需求,咱們列出以下規則:
1.只有類才能被@Factory註解。由於在ShapeFactory中咱們須要實例化Shape對象,雖然@Factory註解聲明瞭Target爲ElementType.TYPE,但接口和枚舉並不符合咱們的要求。 2.被@Factory註解的類中須要有public的構造方法,這樣才能實例化對象。 3.被註解的類必須是type指定的類的子類 4.id須要爲String類型,而且須要在相同type組中惟一 5.具備相同type的註解類會被生成在同一個工廠類中
根據上面的規則,咱們來一步步完成校驗,以下代碼:
private void checkValidClass(FactoryAnnotatedClass item) throws ProcessingException {
TypeElement classElement = item.getTypeElement();
if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
throw new ProcessingException(classElement, "The class %s is not public.",
classElement.getQualifiedName().toString());
}
// 若是是抽象方法則拋出異常終止編譯
if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
throw new ProcessingException(classElement,
"The class %s is abstract. You can't annotate abstract classes with @%",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
}
// 這個類必須是在@Factory.type()中指定的類的子類,不然拋出異常終止編譯
TypeElement superClassElement = mElementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
if (superClassElement.getKind() == ElementKind.INTERFACE) {
// 檢查被註解類是否實現或繼承了@Factory.type()所指定的類型,此處均爲IShape
if (!classElement.getInterfaces().contains(superClassElement.asType())) {
throw new ProcessingException(classElement,
"The class %s annotated with @%s must implement the interface %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
}
} else {
TypeElement currentClass = classElement;
while (true) {
TypeMirror superClassType = currentClass.getSuperclass();
if (superClassType.getKind() == TypeKind.NONE) {
// 向上遍歷父類,直到Object也沒獲取到所需父類,終止編譯拋出異常
throw new ProcessingException(classElement,
"The class %s annotated with @%s must inherit from %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
}
if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
// 校驗經過,終止遍歷
break;
}
currentClass = (TypeElement) mTypeUtils.asElement(superClassType);
}
}
// 檢查是否由public的無參構造方法
for (Element enclosed : classElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructorElement = (ExecutableElement) enclosed;
if (constructorElement.getParameters().size() == 0 &&
constructorElement.getModifiers().contains(Modifier.PUBLIC)) {
// 存在public的無參構造方法,檢查結束
return;
}
}
}
// 爲檢測到public的無參構造方法,拋出異常,終止編譯
throw new ProcessingException(classElement,
"The class %s must provide an public empty default constructor",
classElement.getQualifiedName().toString());
}
複製代碼
若是經過上述校驗,那麼說明被@Factory註解的類是符合咱們的要求的,接下來就能夠處理註解信息來生成所需代碼了。可是本着面向對象的思想,咱們還需聲明FactoryGroupedClasses來存放FactoryAnnotatedClass,而且在這個類中完成了ShapeFactory類的代碼生成。FactoryGroupedClasses 代碼以下:
public class FactoryGroupedClasses {
private static final String SUFFIX = "Factory";
private String qualifiedClassName;
private Map<String, FactoryAnnotatedClass> itemsMap = new LinkedHashMap<>();
public FactoryGroupedClasses(String qualifiedClassName) {
this.qualifiedClassName = qualifiedClassName;
}
public void add(FactoryAnnotatedClass toInsert) {
FactoryAnnotatedClass factoryAnnotatedClass = itemsMap.get(toInsert.getId());
if (factoryAnnotatedClass != null) {
throw new IdAlreadyUsedException(factoryAnnotatedClass);
}
itemsMap.put(toInsert.getId(), toInsert);
}
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
// Generate java file
...
}
}
複製代碼
接下來將全部的FactoryGroupedClasses都添加到集合中去
private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<>();
// ...
FactoryGroupedClasses factoryClass = factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
if (factoryClass == null) {
String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
factoryClasses.put(qualifiedGroupName, factoryClass);
}
factoryClass.add(annotatedClass);
// ...
複製代碼
OK!到目前爲止,全部的準備工做都已經完成了。接下來就是根據註解信息來生成ShapeFactory類了,有沒有很興奮?遍歷factoryClasses集合,並調用FactoryGroupedClasses類的generateCode()方法來生成代碼了:
for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
factoryClass.generateCode(mElementUtils, mFiler);
}
複製代碼
但是,當咱們去掉用generateCode(mElementUtils, mFiler)方法的時候.....納尼?仍是一個空方法,咱們還沒由實現呢!笑哭😂...
到此爲止,咱們惟一剩餘的需求就是生成ShapeFactory類了。上一節中咱們在FactoryProcessor類的init(ProcessingEnvironment processingEnvironment)方法中經過processingEnvironment拿到了Filer,而且咱們也提到經過Filer能夠用來編寫文件,便可以經過Filer來生成咱們所須要的ShapeFactory類。可是,直接使用Filer須要咱們手動拼接類的代碼,極可能一不當心寫錯了一個字母就導致所生成的類是無效的。所以,咱們須要來認識一下JavaPoet這個庫。 JavaPoet是square公司的一個開源框架JavaPoet,由Jake Wharton大神所編寫。JavaPoet能夠用對象的方式來幫助咱們生成類代碼,也就是咱們能只要把要生成的類文件包裝成一個對象,JavaPoet即可以自動幫咱們生成類文件了。關於這個庫的使用就不詳細在這裏講解了,有須要瞭解的能夠到github查看,使用起來很簡單。
好了,步入正題,使用JavaPoet構建並自動生成ShapeFactory類的代碼以下:
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
String factoryClassName = superClassName.getSimpleName() + SUFFIX;
String qualifiedFactoryClassName = qualifiedClassName + SUFFIX;
PackageElement pkg = elementUtils.getPackageOf(superClassName);
String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
MethodSpec.Builder method = MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "id")
.returns(TypeName.get(superClassName.asType()));
method.beginControlFlow("if (id == null)")
.addStatement("throw new IllegalArgumentException($S)", "id is null!")
.endControlFlow();
for (FactoryAnnotatedClass item : itemsMap.values()) {
method.beginControlFlow("if ($S.equals(id))", item.getId())
.addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
.endControlFlow();
}
method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
TypeSpec typeSpec = TypeSpec
.classBuilder(factoryClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(method.build())
.build();
JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
}
複製代碼
好了,如今項目已經能夠幫咱們自動來生成須要的Java文件啦。接下來驗證一下,Build一下項目,切換到project模式下,在app-->build-->generated-->source-->apt-->debug-->(package)-->factory下面就能夠看到ShapeFactory類,以下圖:
到此爲止,本篇文章就告一段落了。相信看完本篇文章必定大有所獲,由於掌握了APT技術以後,再去研究使用APT的第三方框架源碼,必定會遊刃有餘,事半功倍。
因爲本篇文章結構比較複雜且代碼也較多,項目的源碼已經放在文章末尾,可做參考。
參考資料