注解處理器(Annotation Processor)
注解處理器是javac的一個工具萤彩,它用來在編譯時掃描和處理注解(Annotation)。你可以自定義注解,并注冊到相應(yīng)的注解處理器,由注解處理器來處理你的注解慨菱。一個注解的注解處理器,以Java代碼(或者編譯過的字節(jié)碼)作為輸入戴甩,生成文件(通常是.java文件)作為輸出符喝。這些生成的Java代碼是在生成的.java文件中,所以你不能修改已經(jīng)存在的Java類甜孤,例如向已有的類中添加方法洲劣。這些生成的Java文件,會同其他普通的手動編寫的Java源代碼一樣被javac編譯课蔬。
RetentionPolicy.CLASS:注解被保留到class文件囱稽,但jvm加載class文件時候被遺棄,這是默認(rèn)的生命周期二跋;
1. 新建兩個Module
File->New->New Module...->Java Library战惊,填寫好Library name和Java class name后點(diǎn)擊完成。
注意扎即,這里必須為Java庫吞获,不然會找不到j(luò)avax包下的相關(guān)資源。
其中annations是存放注解的谚鄙,processors是存放注解處理器的各拷。
2. 新建編譯時注解
/**
* 編譯時注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface CustomAnnation {
String value();
}
3. 定義注解處理器
定義一個注解處理器 CustomProcessor ,每一個處理器都是繼承于AbstractProcessor闷营,并要求必須復(fù)寫 process() 方法烤黍,通常我們使用復(fù)寫以下4個方法:
/**
* 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù),默認(rèn)不寫就行
*/
public class CustomProcessor extends AbstractProcessor{
/**
* init()方法會被注解處理器工具調(diào)用傻盟,并輸入ProcessingEnvironment參數(shù)速蕊。
* ProcessingEnvironment 提供很多有用的工具類Elements,Types和Filter
* @param processingEnvironment 提供給process用來訪問工具框架的環(huán)境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
/**
* 相當(dāng)于每個處理器的主函數(shù)main()娘赴,在這里寫掃描规哲、評估和處理注解的代碼,以及生成java文件。
* 輸入?yún)?shù)RoundEnvironment可以查詢出包含特定注解的被注解元素
* @param set 請求處理注解類型
* @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境
* @return 返回true,則這些注解已聲明并且不要求后續(xù)Processor處理他們;
* 返回false做祝,則這些注解未聲明并且可能要求后續(xù)Processor處理他們;
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
/**
* 這里必須指定谅将,這個注解處理器是注冊給那個注解的。
* 注意:它的返回值是一個字符串的集合,包含本處理器想要處理注解的注解類型的合法全程。
* @return 注解器所支持的注解類型集合,如果沒有這樣的類型痘番,則返回一個空集合。
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(CustomAnnation.class.getCanonicalName());
return annotations;
}
/**
* 指定Java版本,通常這里使用SourceVersion.latestSupported(),
* 默認(rèn)返回SourceVersion.RELEASE_6
* @return 使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
也可以使用注解的方式來指定Java版本和注解類型
4. 添加注解的處理
/**
* 相當(dāng)于每個處理器的主函數(shù)main()汞舱,在這里寫掃描伍纫、評估和處理注解的代碼,以及生成java文件昂芜。
* 輸入?yún)?shù)RoundEnvironment可以查詢出包含特定注解的被注解元素
* @param set 請求處理注解類型
* @param roundEnvironment 有關(guān)當(dāng)前和以前的信息環(huán)境
* @return 返回true莹规,則這些注解已聲明并且不要求后續(xù)Processor處理他們;
* 返回false,則這些注解未聲明并且可能要求后續(xù)Processor處理他們;
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)返回使用給定的注解類型的元素
for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
System.out.println("----------------------------------");
// 判斷元素的類型為Class
if (element.getKind() == ElementKind.CLASS) {
// 顯示轉(zhuǎn)換元素類型
TypeElement typeElement = (TypeElement) element;
// 輸出元素名稱
System.out.println(typeElement.getSimpleName());
// 輸出注解屬性值
System.out.println(typeElement.getAnnotation(CustomAnnotation.class).value());
}
System.out.println("----------------------------------");
}
return false;
}
5. 注解處理器配置
1泌神、在 processors 庫的 main 目錄下新建 resources 資源文件夾良漱;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾欢际;
3母市、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件;
4损趋、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱患久,包括包路徑;
項(xiàng)目結(jié)構(gòu):
6. 使用
主Module依賴annotations和processors兩個moudle浑槽,然后編譯蒋失,如果未打印出結(jié)果,則Build->ReBuild或者Build->Clean Project->Make Project.
@CustomAnnotation("Hello World")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
7. 打印結(jié)果
8. 存在的問題
我們的主項(xiàng)目中引用了 processors 庫桐玻,但注解處理器只在編譯處理期間需要用到篙挽,編譯處理完后就沒有實(shí)際作用了,而主項(xiàng)目添加了這個庫會引入很多不必要的文件镊靴,為了處理這個問題我們需要引入個插件android-apt铣卡,它能很好地處理這個問題。
9. AutoService
AutoService注解處理器是Google開發(fā)的邑闲,用來生成 META-INF/services/javax.annotation.processing.Processor 文件的算行,你只需要在你定義的注解處理器上添加 @AutoService(Processor.class) 就可以了,簡直不能再方便了苫耸。
- 依賴AutoService庫,在processors的build.gradle文件中添加一下依賴儡陨,并同步工程褪子。
compile 'com.google.auto.service:auto-service:1.0-rc2'
-
使用
圖5.png
10. 問題
這時重新Make下工程也能看到同樣的輸出信息了。但是如果你編譯生成APK時骗村,可能會出現(xiàn)文件重復(fù)的問題嫌褪。解決辦法是在主項(xiàng)目的 build.gradle 加上這么一段:
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
這樣就不會報錯了,這是其中的一個解決方法胚股,還有個更好的解決方法就是用上面提到的android-apt了
11. Android-apt
官網(wǎng)有這么一段描述:
The android-apt plugin assists in working with annotation processors in combination with Android Studio. It has two purposes:
1笼痛、Allow to configure a compile time only annotation processor as a dependency, not including the artifact in the final APK or library
2、Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio
大體來講它有兩個作用:
能在編譯時期去依賴注解處理器并進(jìn)行工作,但在生成 APK 時不會包含任何遺留的東西
能夠輔助 Android Studio 在項(xiàng)目的對應(yīng)目錄中存放注解處理器在編譯期間生成的文件
12. 使用
// 主工程的build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
// replace with the current version of the Android plugin
classpath 'com.android.tools.build:gradle:1.3.0'
// the latest version of the android-apt plugin
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
// 主Module的build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
// ...
compile project(':annotations')
apt project(':processors')
// compile project(':processors')
// compile project(':processors1')
}
13. JavaPoet
在使用編譯時注解時缨伊,需要在編譯期間對注解進(jìn)行處理摘刑,在這里我們沒辦法影響程序的運(yùn)行邏輯,但我們可以進(jìn)行一些需處理刻坊,比如生成一些功能性代碼來輔助程序的開發(fā)枷恕,最常見的是生成.java 源文件,并在程序中可以調(diào)用到生成的文件谭胚。這樣我們就可以用注解來幫助我們處理一些固定邏輯的重復(fù)性代碼(如butterknife)徐块,提高開發(fā)的效率。
通過注解處理器來生成 .java 源文件基本上都會使用javapoet 這個庫灾而,JavaPoet一個是用于產(chǎn)生 .java 源文件的輔助庫胡控,它可以很方便地幫助我們生成需要的.java 源文件,下面來看下具體使用方法旁趟。
使用如下代碼進(jìn)行依賴:
compile "com.squareup:javapoet:1.9.0"
14. 注解生成器
/**
* 每一個注解處理器類都必須有一個空的構(gòu)造函數(shù)铜犬,默認(rèn)不寫就行
*/
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.mazaiting.CustomAnnotation")
public class CustomProcessor extends AbstractProcessor{
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// Filter是個接口,至此吃通過注解處理器創(chuàng)建新文件
mFiler = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (Element element : roundEnvironment.getElementsAnnotatedWith(CustomAnnotation.class)) {
// 判斷元素的類型為Class
if (element.getKind() == ElementKind.CLASS) {
// 顯示轉(zhuǎn)換元素類型
TypeElement typeElement = (TypeElement) element;
// 輸出元素名稱
// System.out.println(typeElement.getSimpleName());
// // 輸出注解屬性值
// System.out.println(typeElement.getAnnotation(CustomAnnotation.class).value());
// 創(chuàng)建main方法
MethodSpec main =
MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
String value = element.getAnnotation(CustomAnnotation.class).value();
String first = value.substring(0, 1);
String className = first.toUpperCase() + value.substring(1, value.length());
TypeSpec valueClass =
TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
// 生成文件
try {
// 生成com.mazaiting.xxx.java
JavaFile javaFile =
JavaFile.builder("com.mazaiting.example", valueClass)
.addFileComment("This codes are generated automatically. Do not modify!")
.build();
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}
15. MainActivity使用
@CustomAnnotation("HelloWorld")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
16. 生成Java類
生成的Java類結(jié)構(gòu)