簡介
APT
全稱Annotation Processing Tool
峰弹,即注解處理器。更確切的說大年,它是javac
的一部分换薄,能夠在編譯期掃描和處理注解,并生成文件专控。
那么使用 APT
有什么好處呢柏蘑?
- 將一些通用的重復(fù)的代碼通過
APT
生成革半,減少開發(fā)工作量,提高開發(fā)效率碘赖; - 在不考慮編譯期耗時的情況下撼班,相較于在運行期通過反射處理的方式,更能提高程序運行效率宫静。
現(xiàn)在很多著名的三方庫都使用了 APT
技術(shù)捌袜,比如 butterknife
候引,ARouter
逛揩,dagger
等。
要使用
APT
麸俘,首先得了解AbstractProcessor.java
息尺,所有自定義的注解處理器都需要繼承這個類。
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;
}
}
有四個重要的方法疾掰。
-
init()
初始化方法搂誉; -
process()
注解處理器處理注解和生成文件的地方,一般邏輯都會寫在這里静檬; -
getSupportedAnnotationTypes()
返回當(dāng)前注解處理器能夠處理的注解信息炭懊; -
getSupportedSourceVersion()
返回當(dāng)前注解處理器支持的版本,沒有特殊要求拂檩,一般都會使用SourceVersion.latestSupported()
侮腹。
ProcessingEnvironment
它是一個接口,通過它可以獲取到配置信息和一些常用的工具類稻励。
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();
}
-
getOptions()
可以獲取到配置信息,比如ARouter
在build.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));
}
-
getMessager()
可以獲取到日志工具類加矛,通過Messager
可以在打印一些日志信息,當(dāng)然你也可以直接使用System.out.println()
來輸出日志煤篙;
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "日志信息");
-
getFiler()
可以獲取到Filer
工具類斟览,用來創(chuàng)建源文件,字節(jié)碼文件等辑奈; -
getElementUtils()
可以獲取到Elements
工具類苛茂,這是一個比較有用的工具類;
// 獲取包元素
PackageElement getPackageElement(CharSequence name);
PackageElement getPackageOf(Element type);
// 是否過時
boolean isDeprecated(Element e);
// 根據(jù)全路徑獲取到某個類的 TypeElement 元素鸠窗,這是非常有用的
TypeElement getTypeElement(CharSequence name);
...
-
getTypeUtils()
獲取到Types
工具類妓羊,這是一個比較有用的工具類;
// t1 是否是 t2 的子類
boolean isSubtype(TypeMirror t1, TypeMirror t2);
...
Element
Element
是注解處理器中比較重要存在稍计。所有經(jīng)過注解處理器掃描后的元素都會被封裝成Element
躁绸。
Element
是一個接口,有五個實現(xiàn)類,分別代表了不同類型的元素涨颜,舉個栗子费韭。
// 1. 包,被封裝為 PackageElement
package com.ppdai;
/*
* 2. 類庭瑰,被封裝為 TypeElement
* 3. 泛型星持,被封裝為 TypeParameterElement
*/
public class Example<T> {
// 4. 變量,被分裝為 VariableElement
private int a;
// 5. 方法弹灭,被封裝為 ExecutableElement
public void b() {
}
}
-
TypeElement
一個類或接口的元素督暂,如果注解處理器處理的對象是類或者接口,那么這個元素將被封裝為TypeElemnet
穷吮; -
Packagelement
表示包元素逻翁; -
VariableElement
表示變量、枚舉捡鱼、方法參數(shù)八回; -
ExecutableElement
表示構(gòu)造函數(shù)、方法驾诈; -
TypeParameterElement
泛型元素缠诅。
實踐
一般注解處理器都會由三個部分組成,compile
乍迄,annotation
管引,api
。
-
compile
一般編寫注解處理器相關(guān)闯两; -
annotation
一般編寫一些注解和一些基礎(chǔ)類褥伴,接口等; -
api
一般會編寫暴露給上層業(yè)務(wù)的封裝漾狼,工具等重慢。
比如ARouter
,ButterKnife
的結(jié)構(gòu)邦投。
接下來我們一步一步實現(xiàn)自己的注解處理器伤锚。
定義 Annotation
這里仿寫
ARouter
的@Autowired
注解擅笔。
新建一個 java library module志衣,并定義自己的 @Autowired
注解。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Autowired {
String name();
String desc() default "";
}
它的結(jié)構(gòu)如下所示猛们。
定義 Annotation Processor
想要在編譯期對注解進行處理念脯,并生成對應(yīng)的文件,需要實現(xiàn)
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<String> getSupportedAnnotationTypes() {
HashSet<String> set = new HashSet<>();
set.add(Autowired.class.getCanonicalName());
return set;
}
}
因為每個類都可以有多個使用 @Autowired
注解的屬性假勿,這里新增一個 categories()
先對所有使用了 @Autowired
的屬性進行分類借嗽,存放到 map
里面。
// 用來存放分類后的數(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
是類對應(yīng)的 TypeElement
转培,value
是當(dāng)前類里面所有使用了 @Autowired
注解的屬性對應(yīng)的 Element
恶导。
然后新增一個 generate()
用來處理分類后的數(shù)據(jù),并生成對應(yīng)的文件浸须。簡單起見惨寿,這里只對 Activity
里面的邏輯進行了處理。
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();
}
}
}
注冊 Annotation Processor
這里提供兩種注冊方式删窒,手動注冊和自動注冊裂垦。
手動注冊
- 在
src/main
下新建一個resources
的目錄; - 在
resources
下新建一個META-INF
的目錄肌索; - 在
META-INF
下新建一個services
的目錄蕉拢; - 在
services
下新建一個javax.annotation.processing.Processor
的文件,并將要注冊的Annotation Processor
的全路徑寫入诚亚。
它的結(jié)構(gòu)大致是這樣子的企量。
自動注冊
auto-service
庫來簡化注冊過程。
修改 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 {
// ...
}
其實 @AutoService
也是通過 Annotation Processor
來實現(xiàn)的恕汇。具體實現(xiàn)我們可以查看 AutoServiceProcessor.java
文件,它的調(diào)用鏈如下 process() -> processImpl() -> generateConfigFiles()
或辖,當(dāng) @AutoService
注解處理完的時候瘾英,會調(diào)用 generateConfigFiles()
, 我們可以看看 generateConfigFiles()
方法的具體實現(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)建一個 android library module,定義一個工具類耳鸯,來調(diào)用生成 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
注解進行數(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)
然后編譯項目所踊,看看我們的注解處理器生成的文件。
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
污筷,點擊跳轉(zhuǎn),查看日志如下乍赫。