概念
註解處理器(Annotation Processor)是javac內置的一個用於編譯時掃描和處理註解(Annotation)的工具。在源代碼編譯階段,通過註解處理器,我們可以獲取源文件內註解(Annotation)相關內容。
APT(Annotation Process Tool),是一種在代碼編譯時處理註解,按照一定的規則,生成相應的java文件,多用於對自定義註解的處理,對運行時的性能影響很小。
用途
由於註解處理器可以在程序編譯階段工作,所以我們可以在編譯期間通過註解處理器進行我們需要的操作。比較常用的用法就是在編譯期間獲取相關注解數據,然後動態生成.java源文件(讓機器幫我們寫代碼),通常是自動產生一些有規律性的重複代碼,解決了手工編寫重複代碼的問題,大大提升編碼效率。
註解處理器可以生成Java代碼,這些生成的Java代碼會組成 .java 文件,但不能修改已經存在的Java類(即不能向已有的類中添加方法)。而這些生成的Java文件,會同時與其他普通的手寫Java源代碼一起被javac編譯。
抽象處理器 AbstractProcessor
每一個註解處理器都要繼承於AbstractProcessor
import javax.annotation.processing.AbstractProcessor;
public class MyAnnotationProcessor extends AbstractProcessor
{
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
{
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
{
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes()
{
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion()
{
return super.getSupportedSourceVersion();
}
}
- init(ProcessingEnvironment processingEnvironment): 每一個註解處理器類都必須有一個空的構造函數。然而,這裏有一個特殊的init()方法,它會被註解處理工具調用,並輸入ProcessingEnviroment參數。
ProcessingEnviroment提供很多有用的工具類Elements,Types和Filer。 - process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 這相當於每個處理器的主函數main()。在這裏寫你的掃描、評估和處理註解的代碼,以及生成Java文件。
輸入參數RoundEnviroment,可以讓你查詢出包含特定註解的被註解元素。
注意:process()函數中不能直接進行異常拋出,否則的話,運行Annotation Processor的進程會異常崩潰,然後彈出一大堆讓人捉摸不清的堆棧調用日誌顯示.
- getSupportedAnnotationTypes(): 這裏必須指定,這個註解處理器是註冊給哪個註解的。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的註解類型的合法全稱,完整的包名+類名。
在這裏定義你的註解處理器註冊到哪些註解上。 - getSupportedSourceVersion(): 用來指定你使用的Java版本。通常這裏返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支持Java 7的話,你也可以返回SourceVersion.RELEASE_7,推薦使用前者。
在Java 7以後,你也可以使用註解來代替getSupportedAnnotationTypes()和getSupportedSourceVersion()
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("baijunyu.com.test_annotation.AAAA")
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
- @AutoService(Processor.class) :向javac註冊我們這個自定義的註解處理器,這樣,在javac編譯時,纔會調用到我們這個自定義的註解處理器方法。
AutoService這裏主要是用來生成 META-INF/services/javax.annotation.processing.Processor文件的。如果不加上這個註解,那麼,你需要自己進行手動配置進行註冊。手動註冊的方式這裏不再闡述,建議直接採用@AutoService(Processor.class)進行自定義註解處理器註冊,簡潔方便
Gradle引入方式:
implementation 'com.google.auto.service:auto-service:1.0-rc4'
基礎工具
在init()中獲得如下引用:
private Logger mLogger;
private Elements mElementUtils;
private Filer mFiler;
private Types mTypeUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mTypeUtils = processingEnv.getTypeUtils();
mLogger = new Logger(processingEnvironment.getMessager());
mElementUtils = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
}
- Elements:一個用來處理Element的工具類
- Types:一個用來處理TypeMirror的工具類
- Filer :使用Filer你可以創建文件
- Messager:用於編譯時在打印日誌信息,在gradle console
在Android Studio使用Annotation Processor
由於Android平臺是基於OpenJDK的,而OpenJDK中不包含Annotation Processor的相關代碼。因此,在使用Annotation Processor時,必須在新建Module時選擇Java Library,處理註解相關的代碼都需要在Java Library模塊下完成。
整個項目的結構
annotation模塊(Java Library) 該模塊存放的是我們自定義的註解,是一個Java Library
compiler模塊 (Java Library) 依賴annotation模塊,處理註解並自動生成代碼等,同樣也是Java Library
app (Android App) 依賴compiler模塊,需要使用annotationProcessor依賴compiler模塊
一個簡單的例子:
1.創建annotation Module,類型爲Java Library,在此模塊的定義我自定義註解類
在annotation中自定義一個註解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
2.創建compiler Module,類型爲Java Library,在此模塊中定義註解處理器
gradle配置:
- 依賴google.auto.service
implementation 'com.google.auto.service:auto-service:1.0-rc4'
- 依賴annotation
implementation project(':test-annotation')
創建一個自定義Annotation Processor繼承於AbstractProcessor
@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : bindViewElements) {
//1.獲取包名
PackageElement packageElement = mElementUtils.getPackageOf(element);
String pkName = packageElement.getQualifiedName().toString();
note(String.format("package = %s", pkName));
//2.獲取包裝類類型
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
String enclosingName = enclosingElement.getQualifiedName().toString();
note(String.format("enclosindClass = %s", enclosingElement));
//因爲BindView只作用於filed,所以這裏可直接進行強轉
VariableElement bindViewElement = (VariableElement) element;
//3.獲取註解的成員變量名
String bindViewFiledName = bindViewElement.getSimpleName().toString();
//3.獲取註解的成員變量類型
String bindViewFiledClassType = bindViewElement.asType().toString();
//4.獲取註解元數據
BindView bindView = element.getAnnotation(BindView.class);
int id = bindView.value();
note(String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id));
//4.生成文件
createFile(enclosingElement, bindViewFiledClassType, bindViewFiledName, id);
return true;
}
return false;
}
private void createFile(TypeElement enclosingElement, String bindViewFiledClassType, String bindViewFiledName, int id) {
String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding", new Element[]{});
Writer writer = jfo.openWriter();
writer.write(brewCode(pkName, bindViewFiledClassType, bindViewFiledName, id));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private String brewCode(String pkName, String bindViewFiledClassType, String bindViewFiledName, int id) {
StringBuilder builder = new StringBuilder();
builder.append("package " + pkName + ";\n\n");
builder.append("//Auto generated by apt,do not modify!!\n\n");
builder.append("public class ViewBinding { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
private void note(String msg) {
mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
}
private void note(String format, Object... args) {
mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
}
}
注:藉助Messager,我們可以在編譯時在 gradle console 輸出日誌.
3.使用註解
app gradle配置:
annotationProcessor project(':test-compiler ')
implementation project(':test-annotation')
在app Module中使用註解
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
rebuild一下,可以在Gradle Console窗口中看到打印結果:
據註解獲取到的數據還生成了一個java文件: