1. 一些基本概念
在開始之前,我們需要聲明一件重要的事情是:我們不是在討論在運行時通過反射機制運行處理的註解,而是在討論在編譯時處理的註解。
編譯時註解跟運行時註解到底區別在什麼地方?其實說大也不大,主要是考慮到性能上面的問題。運行時註解主要是完全依賴於反射,反射的效率比原生的慢,所以在內存比較少,CPU比較爛的機器上會有一些卡頓現像出現。而編譯時註解完全不會有這個問題,因為它在我們編譯過程(java->class)中,通過一些註解標示,去動態生成一些類或者文件,所以跟我們的APK運行完全沒有任何關係,自然就不存在性能上的問題。所以一般比較著名的開源項目如果採用註解功能,通常採用編譯時註解
註解處理器是javac 自帶的一個工具,用來在編譯時期掃描處理註解信息。你可以為某些註解註冊自己的註解處理器。這裡,我假設你已經了解什麼是註解及如何自定義註解。如果你還未了解註解的話,可以查看官方文檔。註解處理器在Java 5 的時候就已經存在了,但直到Java 6 (發佈於2006看十二月)的時候才有可用的API。過了一段時間java的使用者們才意識到註解處理器的強大。所以最近幾年它才開始流行。
一個特定註解的處理器以java 源代碼(或者已編譯的字節碼)作為輸入,然後生成一些文件(通常是.java文件)作為輸出。那意味著什麼呢?你可以生成java 代碼!這些java 代碼在生成的.java文件中。因此你不能改變已經存在的java類,例如添加一個方法。這些生成的java 文件跟其他手動編寫的java 源代碼一樣,將會被javac 編譯。
Annotation processing是在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,然後生成新的Java代碼。新生成的Java代碼最後被編譯成Java字節碼,註解解析器(Annotation Processor)不能改變讀入的Java 類,比如不能加入或刪除Java方法。
2. AbstractProcessor
讓我們來看一下處理器的API。所有的處理器都繼承了AbstractProcessor,如下所示:
package com.example;import java.util.LinkedHashSet;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.ProcessingEnvironment;import javax.annotation.processing.RoundEnvironment;import javax.annotation.processing.SupportedAnnotationTypes;import javax.annotation.processing.SupportedSourceVersion;import javax.lang.model.SourceVersion;import javax.lang.model.element.TypeElement;public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add("com.example.MyAnnotation"); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); }}init(ProcessingEnvironment processingEnv) :所有的註解處理器類都必須有一個無參構造函數。然而,有一個特殊的方法init(),它會被註解處理工具調用,以ProcessingEnvironment作為參數。 ProcessingEnvironment 提供了一些實用的工具類Elements, Types和Filer。我們在後面將會使用到它們。
process(Set<? extends TypeElement> annoations, RoundEnvironment env) :這類似於每個處理器的main()方法。你可以在這個方法裡面編碼實現掃描,處理註解,生成java 文件。使用RoundEnvironment 參數,你可以查詢被特定註解標註的元素(原文:you can query for elements annotated with a certain annotation )。後面我們將會看到詳細內容。
getSupportedAnnotationTypes():在這個方法裡面你必須指定哪些註解應該被註解處理器註冊。注意,它的返回值是一個String集合,包含了你的註解處理器想要處理的註解類型的全稱。換句話說,你在這裡定義你的註解處理器要處理哪些註解。
getSupportedSourceVersion() : 用來指定你使用的java 版本。通常你應該返回SourceVersion.latestSupported() 。不過,如果你有足夠的理由堅持用java 6 的話,你也可以返回SourceVersion.RELEASE_6 。我建議使用SourceVersion.latestSupported() 。在Java 7 中,你也可以使用註解的方式來替代重寫getSupportedAnnotationTypes()和getSupportedSourceVersion() ,如下所示:
@SupportedSourceVersion(value=SourceVersion.RELEASE_7)@SupportedAnnotationTypes({ // Set of full qullified annotation type names "com.example.MyAnnotation", "com.example.AnotherAnnotation" })public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { return false; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); }}由於兼容性問題,特別是對於android ,我建議重寫getSupportedAnnotationTypes()和getSupportedSourceVersion() ,而不是使用@SupportedAnnotationTypes和@SupportedSourceVersion 。
接下來你必須知道的事情是:註解處理器運行在它自己的JVM 中。是的,你沒看錯。 javac 啟動了一個完整的java 虛擬機來運行註解處理器。這意味著什麼?你可以使用任何你在普通java 程序中使用的東西。使用guava! 你可以使用依賴注入工具,比如dagger或者任何其他你想使用的類庫。但不要忘記,即使只是一個小小的處理器,你也應該注意使用高效的算法及設計模式,就像你在開發其他java 程序中所做的一樣。
3. 註冊你的處理器
你可能會問“怎樣註冊我的註解處理器到javac ?”。你必須提供一個.jar文件。就像其他.jar 文件一樣,你將你已經編譯好的註解處理器打包到此文件中。並且,在你的.jar 文件中,你必須打包一個特殊的文件javax.annotation.processing.Processor到META-INF/services目錄下。因此你的.jar 文件目錄結構看起來就你這樣:
MyProcess.jar -com -example -MyProcess.class -META-INF -services -javax.annotation.processing.Processor
javax.annotation.processing.Processor 文件的內容是一個列表,每一行是一個註解處理器的全稱。例如:
com.example.MyProcess
com.example.AnotherProcess
4. 例子:工廠模式
我們要解決的問題是:我們要實現一個pizza 店,這個pizza 店提供給顧客兩種pizza (Margherita 和Calzone),還有甜點Tiramisu(提拉米蘇)。
public interface Meal { public float getPrice();}public class MargheritaPizza implements Meal{ @Override public float getPrice() { return 6.0f; }}public class CalzonePizza implements Meal{ @Override public float getPrice() { return 8.5f; }}public class Tiramisu implements Meal{ @Override public float getPrice() { return 4.5f; }}public class PizzaStore { public Meal order(String mealName) { if (null == mealName) { throw new IllegalArgumentException("name of meal is null!"); } if ("Margherita".equals(mealName)) { return new MargheritaPizza(); } if ("Calzone".equals(mealName)) { return new CalzonePizza(); } if ("Tiramisu".equals(mealName)) { return new Tiramisu(); } throw new IllegalArgumentException("Unknown meal '" + mealName + "'"); } private static String readConsole() { Scanner scanner = new Scanner(System.in); String meal = scanner.nextLine(); scanner.close(); return meal; } public static void main(String[] args) { System.out.println("welcome to pizza store"); PizzaStore pizzaStore = new PizzaStore(); Meal meal = pizzaStore.order(readConsole()); System.out.println("Bill:$" + meal.getPrice()); }}正如你所見,在order()方法中,我們有許多if 條件判斷語句。並且,如果我們添加一種新的pizza 的話,我們就得添加一個新的if 條件判斷。但是等一下,使用註解處理器和工廠模式,我們可以讓一個註解處理器生成這些if 語句。如此一來,我們想要的代碼就像這樣子:
public class PizzaStore { private MealFactory factory = new MealFactory(); public Meal order(String mealName) { return factory.create(mealName); } private static String readConsole() { Scanner scanner = new Scanner(System.in); String meal = scanner.nextLine(); scanner.close(); return meal; } public static void main(String[] args) { System.out.println("welcome to pizza store"); PizzaStore pizzaStore = new PizzaStore(); Meal meal = pizzaStore.order(readConsole()); System.out.println("Bill:$" + meal.getPrice()); }}public class MealFactory { public Meal create(String id) { if (id == null) { throw new IllegalArgumentException("id is null!"); } if ("Calzone".equals(id)) { return new CalzonePizza(); } if ("Tiramisu".equals(id)) { return new Tiramisu(); } if ("Margherita".equals(id)) { return new MargheritaPizza(); } throw new IllegalArgumentException("Unknown id = " + id); }}5. @Factory Annotation
能猜到麼,我們打算使用註解處理器生成MealFactory類。更一般的說,我們想要提供一個註解和一個處理器用來生成工廠類。
讓我們看一下@Factory註解:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.CLASS)public @interface Factory { /** * The name of the factory */ Class<?> type(); /** * The identifier for determining which item should be instantiated */ String id();}思想是這樣的:我們註解那些食物類,使用type()表示這個類屬於哪個工廠,使用id()表示這個類的具體類型。讓我們將@Factory註解應用到這些類上吧:
@Factory(type=MargheritaPizza.class, id="Margherita")public class MargheritaPizza implements Meal{ @Override public float getPrice() { return 6.0f; }}@Factory(type=CalzonePizza.class, id="Calzone")public class CalzonePizza implements Meal{ @Override public float getPrice() { return 8.5f; }}@Factory(type=Tiramisu.class, id="Tiramisu")public class Tiramisu implements Meal{ @Override public float getPrice() { return 4.5f; }}你可能會問,我們是不是可以只將@Factory註解應用到Meal接口上?答案是不行,因為註解是不能被繼承的。即在class X上有註解,class Y extends X,那麼class Y是不會繼承class X上的註解的。在我們編寫處理器之前,需要明確幾點規則:
註解處理器:
public class FactoryProcessor extends AbstractProcessor { private Types typeUtils; private Elements elementUtils; private Filer filer; private Messager messager; private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); typeUtils = processingEnv.getTypeUtils(); elementUtils = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); messager = processingEnv.getMessager(); } @Override public boolean process(Set<? extends TypeElement> arg0, RoundEnvironment arg1) { ... return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotataions = new LinkedHashSet<String>(); annotataions.add(Factory.class.getCanonicalName()); return annotataions; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }}在getSupportedAnnotationTypes()方法中,我們指定@Factory註解將被這個處理器處理。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。