乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Java注解處理器

       Baruch 2017-09-14
      譯文出處:    原文出處:Hannes Dorfmann

      Java中的注解(Annotation)是一個(gè)很神奇的東西,特別現(xiàn)在有很多Android庫(kù)都是使用注解的方式來(lái)實(shí)現(xiàn)的。一直想詳細(xì)了解一下其中的原理。很有幸閱讀到一篇詳細(xì)解釋編寫注解處理器的文章。本文的原文是ANNOTATION PROCESSING 101,作者是Hannes Dorfmann。這是一篇好文,忍不住翻譯學(xué)習(xí)一下。以下是翻譯。


      在這篇文章中,我將闡述怎樣寫一個(gè)注解處理器(Annotation Processor)。在這篇教程中,首先,我將向您解釋什么是注解器,你可以利用這個(gè)強(qiáng)大的工具做什么以及不能做什么;然后,我將一步一步實(shí)現(xiàn)一個(gè)簡(jiǎn)單的注解器。

      一些基本概念

      在開(kāi)始之前,我們首先申明一個(gè)非常重要的問(wèn)題:我們并不討論那些在運(yùn)行時(shí)(Runtime)通過(guò)反射機(jī)制運(yùn)行處理的注解,而是討論在編譯時(shí)(Compile time)處理的注解。

      注解處理器是一個(gè)在javac中的,用來(lái)編譯時(shí)掃描和處理的注解的工具。你可以為特定的注解,注冊(cè)你自己的注解處理器。到這里,我假設(shè)你已經(jīng)知道什么是注解,并且知道怎么申明的一個(gè)注解類型。如果你不熟悉注解,你可以在這官方文檔中得到更多信息。注解處理器在Java 5開(kāi)始就有了,但是從Java 6(2006年12月發(fā)布)開(kāi)始才有可用的API。過(guò)了一些時(shí)間,Java世界才意識(shí)到注解處理器的強(qiáng)大作用,所以它到最近幾年才流行起來(lái)。

      一個(gè)注解的注解處理器,以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成文件(通常是.java文件)作為輸出。這具體的含義什么呢?你可以生成Java代碼!這些生成的Java代碼是在生成的.java文件中,所以你不能修改已經(jīng)存在的Java類,例如向已有的類中添加方法。這些生成的Java文件,會(huì)同其他普通的手動(dòng)編寫的Java源代碼一樣被javac編譯。

      虛處理器AbstractProcessor

      我們首先看一下處理器的API。每一個(gè)處理器都是繼承于AbstractProcessor,如下所示:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.example;
      public class MyProcessor extends AbstractProcessor {
          @Override
          public synchronized void init(ProcessingEnvironment env){ }
          @Override
          public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
          @Override
          public Set<String> getSupportedAnnotationTypes() { }
          @Override
          public SourceVersion getSupportedSourceVersion() { }
      }
      • init(ProcessingEnvironment env): 每一個(gè)注解處理器類都必須有一個(gè)空的構(gòu)造函數(shù)。然而,這里有一個(gè)特殊的init()方法,它會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類ElementsTypesFiler。后面我們將看到詳細(xì)的內(nèi)容。
      • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 這相當(dāng)于每個(gè)處理器的主函數(shù)main()。你在這里寫你的掃描、評(píng)估和處理注解的代碼,以及生成Java文件。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。后面我們將看到詳細(xì)的內(nèi)容。
      • getSupportedAnnotationTypes(): 這里你必須指定,這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的。注意,它的返回值是一個(gè)字符串的集合,包含本處理器想要處理的注解類型的合法全稱。換句話說(shuō),你在這里定義你的注解處理器注冊(cè)到哪些注解上。
      • getSupportedSourceVersion(): 用來(lái)指定你使用的Java版本。通常這里返回SourceVersion.latestSupported()。然而,如果你有足夠的理由只支持Java 6的話,你也可以返回SourceVersion.RELEASE_6。我推薦你使用前者。

      在Java 7中,你也可以使用注解來(lái)代替getSupportedAnnotationTypes()getSupportedSourceVersion(),像這樣:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @SupportedSourceVersion(SourceVersion.latestSupported())
      @SupportedAnnotationTypes({
         // 合法注解全名的集合
       })
      public class MyProcessor extends AbstractProcessor {
          @Override
          public synchronized void init(ProcessingEnvironment env){ }
          @Override
          public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
      }

      因?yàn)榧嫒莸脑?,特別是針對(duì)Android平臺(tái),我建議使用重載getSupportedAnnotationTypes()getSupportedSourceVersion()方法代替@SupportedAnnotationTypes@SupportedSourceVersion。

      接下來(lái)的你必須知道的事情是,注解處理器是運(yùn)行它自己的虛擬機(jī)JVM中。是的,你沒(méi)有看錯(cuò),javac啟動(dòng)一個(gè)完整Java虛擬機(jī)來(lái)運(yùn)行注解處理器。這對(duì)你意味著什么?你可以使用任何你在其他java應(yīng)用中使用的的東西。使用guava。如果你愿意,你可以使用依賴注入工具,例如dagger或者其他你想要的類庫(kù)。但是不要忘記,即使是一個(gè)很小的處理,你也要像其他Java應(yīng)用一樣,注意算法效率,以及設(shè)計(jì)模式。

      注冊(cè)你的處理器

      你可能會(huì)問(wèn),我怎樣處理器MyProcessor到j(luò)avac中。你必須提供一個(gè).jar文件。就像其他.jar文件一樣,你打包你的注解處理器到此文件中。并且,在你的jar中,你需要打包一個(gè)特定的文件javax.annotation.processing.ProcessorMETA-INF/services路徑下。所以,你的.jar文件看起來(lái)就像下面這樣:

      打包進(jìn)MyProcessor.jar中的javax.annotation.processing.Processor的內(nèi)容是,注解處理器的合法的全名列表,每一個(gè)元素?fù)Q行分割:

      1
      2
      3
      com.example.MyProcessor 
      com.foo.OtherProcessor 
      net.blabla.SpecialProcessor

      MyProcessor.jar放到你的builpath中,javac會(huì)自動(dòng)檢查和讀取javax.annotation.processing.Processor中的內(nèi)容,并且注冊(cè)MyProcessor作為注解處理器。

      例子:工廠模式

      是時(shí)候來(lái)說(shuō)一個(gè)實(shí)際的例子了。我們將使用maven工具來(lái)作為我們的編譯系統(tǒng)和依賴管理工具。如果你不熟悉maven,不用擔(dān)心,因?yàn)閙aven不是必須的。本例子的完成代碼在Github上。

      開(kāi)始之前,我必須說(shuō),要為這個(gè)教程找到一個(gè)需要用注解處理器解決的簡(jiǎn)單問(wèn)題,實(shí)在并不容易。這里我們將實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的工廠模式(不是抽象工廠模式)。這將對(duì)注解處理器的API做一個(gè)非常簡(jiǎn)明的介紹。所以,這個(gè)問(wèn)題的程序并不是那么有用,也不是一個(gè)真實(shí)世界的例子。所以在此申明,你將學(xué)習(xí)關(guān)于注解處理過(guò)程的相關(guān)內(nèi)容,而不是設(shè)計(jì)模式。

      我們將要解決的問(wèn)題是:我們將實(shí)現(xiàn)一個(gè)披薩店,這個(gè)披薩店給消費(fèi)者提供兩種披薩(“Margherita”和“Calzone”)以及提拉米蘇甜點(diǎn)(Tiramisu)。

      看一下如下的代碼,不需要做任何更多的解釋:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      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;
        }
      }

      為了在我們的披薩店PizzsStore下訂單,消費(fèi)者需要輸入餐(Meal)的名字。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      public class PizzaStore {
        public Meal order(String mealName) {
          if (mealName == null) {
            throw new IllegalArgumentException("Name of the 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 + "'");
        }
        public static void main(String[] args) throws IOException {
          PizzaStore pizzaStore = new PizzaStore();
          Meal meal = pizzaStore.order(readConsole());
          System.out.println("Bill: $" + meal.getPrice());
        }
      }

      正如你所見(jiàn),在order()方法中,我們有很多的if語(yǔ)句,并且如果我們每添加一種新的披薩,我們都要添加一條新的if語(yǔ)句。但是等一下,使用注解處理和工廠模式,我們可以讓注解處理器來(lái)幫我們自動(dòng)生成這些if語(yǔ)句。如此以來(lái),我們期望的是如下的代碼:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class PizzaStore {
        private MealFactory factory = new MealFactory();
        public Meal order(String mealName) {
          return factory.create(mealName);
        }
        public static void main(String[] args) throws IOException {
          PizzaStore pizzaStore = new PizzaStore();
          Meal meal = pizzaStore.order(readConsole());
          System.out.println("Bill: $" + meal.getPrice());
        }
      }

      MealFactory應(yīng)該是如下的樣子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      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);
        }
      }

      @Factory注解

      你能猜到么:我們想用注解處理器自動(dòng)生成MealFactory。更一般的說(shuō),我們將想要提供一個(gè)注解和一個(gè)處理器來(lái)生成工廠類。

      我們先來(lái)看一下@Factory注解:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS)
      public @interface Factory {
        /**
         * 工廠的名字
         */
        Class type();
        /**
         * 用來(lái)表示生成哪個(gè)對(duì)象的唯一id
         */
        String id();
      }

      想法是這樣的:我們將使用同樣的type()注解那些屬于同一個(gè)工廠的類,并且用注解的id()做一個(gè)映射,例如從"Calzone"映射到"ClzonePizza"類。我們應(yīng)用@Factory注解到我們的類中,如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Factory(
          id = "Margherita",
          type = Meal.class
      )
      public class MargheritaPizza implements Meal {
        @Override public float getPrice() {
          return 6f;
        }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Factory(
          id = "Calzone",
          type = Meal.class
      )
      public class CalzonePizza implements Meal {
        @Override public float getPrice() {
          return 8.5f;
        }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Factory(
          id = "Tiramisu",
          type = Meal.class
      )
      public class Tiramisu implements Meal {
        @Override public float getPrice() {
          return 4.5f;
        }
      }

      你可能會(huì)問(wèn)你自己,我們是否可以只把@Factory注解應(yīng)用到我們的Meal接口上?答案是,注解是不能繼承的。一個(gè)類class X被注解,并不意味著它的子類class Y extends X會(huì)自動(dòng)被注解。在我們開(kāi)始寫處理器的代碼之前,我們先規(guī)定如下一些規(guī)則:

      1. 只有類可以被@Factory注解,因?yàn)榻涌诨蛘叱橄箢惒⒉荒苡?code>new操作實(shí)例化;
      2. @Factory注解的類,必須至少提供一個(gè)公開(kāi)的默認(rèn)構(gòu)造器(即沒(méi)有參數(shù)的構(gòu)造函數(shù))。否者我們沒(méi)法實(shí)例化一個(gè)對(duì)象。
      3. @Factory注解的類必須直接或者間接的繼承于type()指定的類型;
      4. 具有相同的type的注解類,將被聚合在一起生成一個(gè)工廠類。這個(gè)生成的類使用Factory后綴,例如type = Meal.class,將生成MealFactory工廠類;
      5. id只能是String類型,并且在同一個(gè)type組中必須唯一。

      處理器

      我將通過(guò)添加代碼和一段解釋的方法,一步一步的引導(dǎo)你來(lái)構(gòu)建我們的處理器。省略號(hào)(...)表示省略那些已經(jīng)討論過(guò)的或者將在后面的步驟中討論的代碼,目的是為了讓我們的代碼有更好的可讀性。正如我們前面說(shuō)的,我們完整的代碼可以在Github上找到。好了,讓我們來(lái)看一下我們的處理器類FactoryProcessor的骨架:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      @AutoService(Processor.class)
      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 Set<String> getSupportedAnnotationTypes() {
          Set<String> annotataions = new LinkedHashSet<String>();
          annotataions.add(Factory.class.getCanonicalName());
          return annotataions;
        }
        @Override
        public SourceVersion getSupportedSourceVersion() {
          return SourceVersion.latestSupported();
        }
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
          ...
        }
      }

      你看到在代碼的第一行是@AutoService(Processor.class),這是什么?這是一個(gè)其他注解處理器中引入的注解。AutoService注解處理器是Google開(kāi)發(fā)的,用來(lái)生成META-INF/services/javax.annotation.processing.Processor文件的。是的,你沒(méi)有看錯(cuò),我們可以在注解處理器中使用注解。非常方便,難道不是么?在getSupportedAnnotationTypes()中,我們指定本處理器將處理@Factory注解。

      Elements和TypeMirrors

      init()中我們獲得如下引用:

      • Elements:一個(gè)用來(lái)處理Element的工具類(后面將做詳細(xì)說(shuō)明);
      • Types:一個(gè)用來(lái)處理TypeMirror的工具類(后面將做詳細(xì)說(shuō)明);
      • Filer:正如這個(gè)名字所示,使用Filer你可以創(chuàng)建文件。

      在注解處理過(guò)程中,我們掃描所有的Java源文件。源代碼的每一個(gè)部分都是一個(gè)特定類型的Element。換句話說(shuō):Element代表程序的元素,例如包、類或者方法。每個(gè)Element代表一個(gè)靜態(tài)的、語(yǔ)言級(jí)別的構(gòu)件。在下面的例子中,我們通過(guò)注釋來(lái)說(shuō)明這個(gè):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package com.example;    // PackageElement
      public class Foo {        // TypeElement
          private int a;      // VariableElement
          private Foo other;  // VariableElement
          public Foo () {}    // ExecuteableElement
          public void setA (  // ExecuteableElement
                           int newA   // TypeElement
                           ) {}
      }

      你必須換個(gè)角度來(lái)看源代碼,它只是結(jié)構(gòu)化的文本,他不是可運(yùn)行的。你可以想象它就像你將要去解析的XML文件一樣(或者是編譯器中抽象的語(yǔ)法樹(shù))。就像XML解釋器一樣,有一些類似DOM的元素。你可以從一個(gè)元素導(dǎo)航到它的父或者子元素上。

      舉例來(lái)說(shuō),假如你有一個(gè)代表public class Foo類的TypeElement元素,你可以遍歷它的孩子,如下:

      1
      2
      3
      4
      TypeElement fooClass = ... ; 
      for (Element e : fooClass.getEnclosedElements()){ // iterate over children 
          Element parent = e.getEnclosingElement();  // parent == fooClass
      }

      正如你所見(jiàn),Element代表的是源代碼。TypeElement代表的是源代碼中的類型元素,例如類。然而,TypeElement并不包含類本身的信息。你可以從TypeElement中獲取類的名字,但是你獲取不到類的信息,例如它的父類。這種信息需要通過(guò)TypeMirror獲取。你可以通過(guò)調(diào)用elements.asType()獲取元素的TypeMirror。

      搜索@Factory注解

      我們來(lái)一步一步實(shí)現(xiàn)process()方法。首先,我們從搜索被注解了@Factory的類開(kāi)始:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      @AutoService(Processor.class)
      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 boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
          // 遍歷所有被注解了@Factory的元素
          for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
                ...
          }
        }
       ...
      }

      這里并沒(méi)有什么高深的技術(shù)。roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。你可能已經(jīng)注意到,我們并沒(méi)有說(shuō)“所有被注解了@Factory的類的列表”,因?yàn)樗娴氖欠祷?code>Element的列表。請(qǐng)記?。?code>Element可以是類、方法、變量等。所以,接下來(lái),我們必須檢查這些Element是否是一個(gè)類:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
          for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
            // 檢查被注解為@Factory的元素是否是一個(gè)類
            if (annotatedElement.getKind() != ElementKind.CLASS) {
                  ...
            }
         }
         ...
      }

      為什么要這么做?我們要確保只有class元素被我們的處理器處理。前面我們已經(jīng)學(xué)習(xí)到類是用TypeElement表示。我們?yōu)槭裁床贿@樣判斷呢if (! (annotatedElement instanceof TypeElement) )?這是錯(cuò)誤的,因?yàn)榻涌冢╥nterface)類型也是TypeElement。所以在注解處理器中,我們要避免使用instanceof,而是配合TypeMirror使用EmentKind或者TypeKind。

      錯(cuò)誤處理

      init()中,我們也獲得了一個(gè)Messager對(duì)象的引用。Messager提供給注解處理器一個(gè)報(bào)告錯(cuò)誤、警告以及提示信息的途徑。它不是注解處理器開(kāi)發(fā)者的日志工具,而是用來(lái)寫一些信息給使用此注解器的第三方開(kāi)發(fā)者的。在官方文檔中描述了消息的不同級(jí)別。非常重要的是Kind.ERROR,因?yàn)檫@種類型的信息用來(lái)表示我們的注解處理器處理失敗了。很有可能是第三方開(kāi)發(fā)者錯(cuò)誤的使用了@Factory注解(例如,給接口使用了@Factory注解)。這個(gè)概念和傳統(tǒng)的Java應(yīng)用有點(diǎn)不一樣,在傳統(tǒng)Java應(yīng)用中我們可能就拋出一個(gè)異常Exception。如果你在process()中拋出一個(gè)異常,那么運(yùn)行注解處理器的JVM將會(huì)崩潰(就像其他Java應(yīng)用一樣),使用我們注解處理器FactoryProcessor第三方開(kāi)發(fā)者將會(huì)從javac中得到非常難懂的出錯(cuò)信息,因?yàn)樗現(xiàn)actoryProcessor的堆棧跟蹤(Stacktace)信息。因此,注解處理器就有一個(gè)Messager類,它能夠打印非常優(yōu)美的錯(cuò)誤信息。除此之外,你還可以連接到出錯(cuò)的元素。在像IntelliJ這種現(xiàn)代的IDE(集成開(kāi)發(fā)環(huán)境)中,第三方開(kāi)發(fā)者可以直接點(diǎn)擊錯(cuò)誤信息,IDE將會(huì)直接跳轉(zhuǎn)到第三方開(kāi)發(fā)者項(xiàng)目的出錯(cuò)的源文件的相應(yīng)的行。

      我們重新回到process()方法的實(shí)現(xiàn)。如果遇到一個(gè)非類類型被注解@Factory,我們發(fā)出一個(gè)出錯(cuò)信息:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
          for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
            // 檢查被注解為@Factory的元素是否是一個(gè)類
            if (annotatedElement.getKind() != ElementKind.CLASS) {
              error(annotatedElement, "Only classes can be annotated with @%s",
                  Factory.class.getSimpleName());
              return true; // 退出處理
            }
            ...
          }
      private void error(Element e, String msg, Object... args) { 
          messager.printMessage(
              Diagnostic.Kind.ERROR,
              String.format(msg, args),
              e);
        }
      }

      讓Messager顯示相關(guān)出錯(cuò)信息,更重要的是注解處理器程序必須完成運(yùn)行而不崩潰,這就是為什么在調(diào)用error()后直接return。如果我們不直接返回,process()將繼續(xù)運(yùn)行,因?yàn)?code>messager.printMessage( Diagnostic.Kind.ERROR)不會(huì)停止此進(jìn)程。因此,如果我們?cè)诖蛴″e(cuò)誤信息以后不返回的話,我們很可能就會(huì)運(yùn)行到一個(gè)NullPointerException等。就像我們前面說(shuō)的,如果我們繼續(xù)運(yùn)行process(),問(wèn)題是如果在process()中拋出一個(gè)未處理的異常,javac將會(huì)打印出內(nèi)部的NullPointerException,而不是Messager中的錯(cuò)誤信息。

      數(shù)據(jù)模型

      在繼續(xù)檢查被注解@Fractory的類是否滿足我們上面說(shuō)的5條規(guī)則之前,我們將介紹一個(gè)讓我們更方便繼續(xù)處理的數(shù)據(jù)結(jié)構(gòu)。有時(shí)候,一個(gè)問(wèn)題或者解釋器看起來(lái)如此簡(jiǎn)單,以至于程序員傾向于用一個(gè)面向過(guò)程方式來(lái)寫整個(gè)處理器。但是你知道嗎?一個(gè)注解處理器任然是一個(gè)Java程序,所以我們需要使用面向?qū)ο缶幊?、接口、設(shè)計(jì)模式,以及任何你將在其他普通Java程序中使用的技巧。

      我們的FactoryProcessor非常簡(jiǎn)單,但是我們?nèi)匀幌胍岩恍┬畔⒋鏋閷?duì)象。在FactoryAnnotatedClass中,我們保存被注解類的數(shù)據(jù),比如合法的類的名字,以及@Factory注解本身的一些信息。所以,我們保存TypeElement和處理過(guò)的@Factory注解:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      public class FactoryAnnotatedClass {
        private TypeElement annotatedClassElement;
        private String qualifiedSuperClassName;
        private String simpleTypeName;
        private String id;
        public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException {
          this.annotatedClassElement = classElement;
          Factory annotation = classElement.getAnnotation(Factory.class);
          id = annotation.id();
          if (StringUtils.isEmpty(id)) {
            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();
            qualifiedSuperClassName = clazz.getCanonicalName();
            simpleTypeName = clazz.getSimpleName();
          } catch (MirroredTypeException mte) {
            DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
            TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
            qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
            simpleTypeName = classTypeElement.getSimpleName().toString();
          }
        }
        /**
         * 獲取在{@link Factory#id()}中指定的id
         * return the id
         */
        public String getId() {
          return id;
        }
        /**
         * 獲取在{@link Factory#type()}指定的類型合法全名
         *
         * @return qualified name
         */
        public String getQualifiedFactoryGroupName() {
          return qualifiedSuperClassName;
        }
        /**
         * 獲取在{@link Factory#type()}{@link Factory#type()}指定的類型的簡(jiǎn)單名字
         *
         * @return qualified name
         */
        public String getSimpleFactoryGroupName() {
          return simpleTypeName;
        }
        /**
         * 獲取被@Factory注解的原始元素
         */
        public TypeElement getTypeElement() {
          return annotatedClassElement;
        }
      }

      代碼很多,但是最重要的部分是在構(gòu)造函數(shù)中。其中你能找到如下的代碼:

      1
      2
      3
      4
      5
      6
      7
      8
      Factory annotation = classElement.getAnnotation(Factory.class); 
      id = annotation.id(); // Read the id value (like "Calzone" or "Tiramisu")
      if (StringUtils.isEmpty(id)) { 
          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()));
      }

      這里我們獲取@Factory注解,并且檢查id是否為空?如果為空,我們將拋出IllegalArgumentException異常。你可能感到疑惑的是,前面我們說(shuō)了不要拋出異常,而是使用Messager。這里仍然不矛盾。我們拋出內(nèi)部的異常,你在將在后面看到會(huì)在process()中捕獲這個(gè)異常。我這樣做出于一下兩個(gè)原因:

      1. 我想示意我們應(yīng)該像普通的Java程序一樣編碼。拋出和捕獲異常是非常好的Java編程實(shí)踐;
      2. 如果我們想要在FactoryAnnotatedClass中打印信息,我需要也傳入Messager對(duì)象,并且我們?cè)阱e(cuò)誤處理一節(jié)中已經(jīng)提到,為了打印Messager信息,我們必須成功停止處理器運(yùn)行。如果我們使用Messager打印了錯(cuò)誤信息,我們?cè)鯓痈嬷?code>process()出現(xiàn)了錯(cuò)誤呢?最容易,并且我認(rèn)為最直觀的方式就是拋出一個(gè)異常,然后讓process()捕獲之。

      接下來(lái),我們將獲取@Fractory注解中的type成員。我們比較關(guān)心的是合法的全名:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      try
            Class<?> clazz = annotation.type();
            qualifiedGroupClassName = clazz.getCanonicalName();
            simpleFactoryGroupName = clazz.getSimpleName();
      } catch (MirroredTypeException mte) {
            DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
            TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
            qualifiedGroupClassName = classTypeElement.getQualifiedName().toString();
            simpleFactoryGroupName = classTypeElement.getSimpleName().toString();
      }

      這里有一點(diǎn)小麻煩,因?yàn)檫@里的類型是一個(gè)java.lang.Class。這就意味著,他是一個(gè)真正的Class對(duì)象。因?yàn)樽⒔馓幚硎窃诰幾gJava源代碼之前。我們需要考慮如下兩種情況:

      1. 這個(gè)類已經(jīng)被編譯:這種情況是:如果第三方.jar包含已編譯的被@Factory注解.class文件。在這種情況下,我們可以想try中那塊代碼中所示直接獲取Class
      2. 這個(gè)還沒(méi)有被編譯:這種情況是我們嘗試編譯被@Fractory注解的源代碼。這種情況下,直接獲取Class會(huì)拋出MirroredTypeException異常。幸運(yùn)的是,MirroredTypeException包含一個(gè)TypeMirror,它表示我們未編譯類。因?yàn)槲覀円呀?jīng)知道它必定是一個(gè)類類型(我們已經(jīng)在前面檢查過(guò)),我們可以直接強(qiáng)制轉(zhuǎn)換為DeclaredType,然后讀取TypeElement來(lái)獲取合法的名字。

      好了,我們現(xiàn)在還需要一個(gè)數(shù)據(jù)結(jié)構(gòu)FactoryGroupedClasses,它將簡(jiǎn)單的組合所有的FactoryAnnotatedClasses到一起。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      public class FactoryGroupedClasses {
        private String qualifiedClassName;
        private Map<String, FactoryAnnotatedClass> itemsMap =
            new LinkedHashMap<String, FactoryAnnotatedClass>();
        public FactoryGroupedClasses(String qualifiedClassName) {
          this.qualifiedClassName = qualifiedClassName;
        }
        public void add(FactoryAnnotatedClass toInsert) throws IdAlreadyUsedException {
          FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());
          if (existing != null) {
            throw new IdAlreadyUsedException(existing);
          }
          itemsMap.put(toInsert.getId(), toInsert);
        }
        public void generateCode(Elements elementUtils, Filer filer) throws IOException {
          ...
        }
      }

      正如你所見(jiàn),這是一個(gè)基本的Map<String, FactoryAnnotatedClass>,這個(gè)映射表用來(lái)映射@Factory.id()到FactoryAnnotatedClass。我們選擇Map這個(gè)數(shù)據(jù)類型,是因?yàn)槲覀円_保每個(gè)id是唯一的,我們可以很容易通過(guò)map查找實(shí)現(xiàn)。generateCode()方法將被用來(lái)生成工廠類代碼(將在后面討論)。

      匹配標(biāo)準(zhǔn)

      我們繼續(xù)實(shí)現(xiàn)process()方法。接下來(lái)我們想要檢查被注解的類必須有只要一個(gè)公開(kāi)的構(gòu)造函數(shù),不是抽象類,繼承于特定的類型,以及是一個(gè)公開(kāi)類:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      public class FactoryProcessor extends AbstractProcessor {
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
          for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
            ...
            // 因?yàn)槲覀円呀?jīng)知道它是ElementKind.CLASS類型,所以可以直接強(qiáng)制轉(zhuǎn)換
            TypeElement typeElement = (TypeElement) annotatedElement;
            try {
              FactoryAnnotatedClass annotatedClass =
                  new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException
              if (!isValidClass(annotatedClass)) {
                return true; // 已經(jīng)打印了錯(cuò)誤信息,退出處理過(guò)程
               }
             } catch (IllegalArgumentException e) {
              // @Factory.id()為空
              error(typeElement, e.getMessage());
              return true;
             }
                ...
         }
       private boolean isValidClass(FactoryAnnotatedClass item) {
          // 轉(zhuǎn)換為TypeElement, 含有更多特定的方法
          TypeElement classElement = item.getTypeElement();
          if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
            error(classElement, "The class %s is not public.",
                classElement.getQualifiedName().toString());
            return false;
          }
          // 檢查是否是一個(gè)抽象類
          if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
            error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",
                classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
            return false;
          }
          // 檢查繼承關(guān)系: 必須是@Factory.type()指定的類型子類
          TypeElement superClassElement =
              elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
          if (superClassElement.getKind() == ElementKind.INTERFACE) {
            // 檢查接口是否實(shí)現(xiàn)了                                       if(!classElement.getInterfaces().contains(superClassElement.asType())) {
              error(classElement, "The class %s annotated with @%s must implement the interface %s",
                  classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                  item.getQualifiedFactoryGroupName());
              return false;
            }
          } else {
            // 檢查子類
            TypeElement currentClass = classElement;
            while (true) {
              TypeMirror superClassType = currentClass.getSuperclass();
              if (superClassType.getKind() == TypeKind.NONE) {
                // 到達(dá)了基本類型(java.lang.Object), 所以退出
                error(classElement, "The class %s annotated with @%s must inherit from %s",
                    classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                    item.getQualifiedFactoryGroupName());
                return false;
              }
              if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
                // 找到了要求的父類
                break;
              }
              // 在繼承樹(shù)上繼續(xù)向上搜尋
              currentClass = (TypeElement) typeUtils.asElement(superClassType);
            }
          }
          // 檢查是否提供了默認(rèn)公開(kāi)構(gòu)造函數(shù)
          for (Element enclosed : classElement.getEnclosedElements()) {
            if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
              ExecutableElement constructorElement = (ExecutableElement) enclosed;
              if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
                  .contains(Modifier.PUBLIC)) {
                // 找到了默認(rèn)構(gòu)造函數(shù)
                return true;
              }
            }
          }
          // 沒(méi)有找到默認(rèn)構(gòu)造函數(shù)
          error(classElement, "The class %s must provide an public empty default constructor",
              classElement.getQualifiedName().toString());
          return false;
        }
      }

      我們這里添加了isValidClass()方法,來(lái)檢查是否我們所有的規(guī)則都被滿足了:

      • 必須是公開(kāi)類:classElement.getModifiers().contains(Modifier.PUBLIC)
      • 必須是非抽象類:classElement.getModifiers().contains(Modifier.ABSTRACT)
      • 必須是@Factoy.type()指定的類型的子類或者接口的實(shí)現(xiàn):首先我們使用elementUtils.getTypeElement(item.getQualifiedFactoryGroupName())創(chuàng)建一個(gè)傳入的Class(@Factoy.type())的元素。是的,你可以僅僅通過(guò)已知的合法類名來(lái)直接創(chuàng)建TypeElement(使用TypeMirror)。接下來(lái)我們檢查它是一個(gè)接口還是一個(gè)類:superClassElement.getKind() == ElementKind.INTERFACE。所以我們這里有兩種情況:如果是接口,就判斷classElement.getInterfaces().contains(superClassElement.asType());如果是類,我們就必須使用currentClass.getSuperclass()掃描繼承層級(jí)。注意,整個(gè)檢查也可以使用typeUtils.isSubtype()來(lái)實(shí)現(xiàn)。
      • 類必須有一個(gè)公開(kāi)的默認(rèn)構(gòu)造函數(shù):我們遍歷所有的閉元素classElement.getEnclosedElements(),然后檢查ElementKind.CONSTRUCTOR、Modifier.PUBLIC以及constructorElement.getParameters().size() == 0。

      如果所有這些條件都滿足,isValidClass()返回true,否者就打印錯(cuò)誤信息,并且返回false

      組合被注解的類

      一旦我們檢查isValidClass()成功,我們將添加FactoryAnnotatedClass到對(duì)應(yīng)的FactoryGroupedClasses中,如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      public class FactoryProcessor extends AbstractProcessor {
         private Map<String, FactoryGroupedClasses> factoryClasses =
            new LinkedHashMap<String, FactoryGroupedClasses>();
       @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            ...
            try {
              FactoryAnnotatedClass annotatedClass =
                  new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException
                if (!isValidClass(annotatedClass)) {
                return true; // 錯(cuò)誤信息被打印,退出處理流程
              }
              // 所有檢查都沒(méi)有問(wèn)題,所以可以添加了
              FactoryGroupedClasses factoryClass =
              factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
              if (factoryClass == null) {
                String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
                factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
                factoryClasses.put(qualifiedGroupName, factoryClass);
              }
              // 如果和其他的@Factory標(biāo)注的類的id相同沖突,
              // 拋出IdAlreadyUsedException異常
              factoryClass.add(annotatedClass);
            } catch (IllegalArgumentException e) {
              // @Factory.id()為空 --> 打印錯(cuò)誤信息
              error(typeElement, e.getMessage());
              return true;
            } catch (IdAlreadyUsedException e) {
              FactoryAnnotatedClass existing = e.getExisting();
              // 已經(jīng)存在
              error(annotatedElement,
                  "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",
                  typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                  existing.getTypeElement().getQualifiedName().toString());
              return true;
            }
          }
          ...
      }

      代碼生成

      我們已經(jīng)手機(jī)了所有的被@Factory注解的類保存到為FactoryAnnotatedClass,并且組合到了FactoryGroupedClasses?,F(xiàn)在我們將為每個(gè)工廠生成Java文件了:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 
          ...
        try {
              for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
                factoryClass.generateCode(elementUtils, filer);
              }
          } catch (IOException e) {
              error(null, e.getMessage());
          }
          return true;
      }

      寫Java文件,和寫其他普通文件沒(méi)有什么兩樣。使用Filer提供的Writer對(duì)象,我們可以連接字符串來(lái)寫我們生成的Java代碼。幸運(yùn)的是,Square公司(因?yàn)樘峁┝嗽S多非常優(yōu)秀的開(kāi)源項(xiàng)目二非常有名)給我們提供了JavaWriter,這是一個(gè)高級(jí)的生成Java代碼的庫(kù):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      public class FactoryGroupedClasses {
        /**
         * 將被添加到生成的工廠類的名字中
         */
        private static final String SUFFIX = "Factory";
        private String qualifiedClassName;
        private Map<String, FactoryAnnotatedClass> itemsMap =
            new LinkedHashMap<String, FactoryAnnotatedClass>();
          ...
        public void generateCode(Elements elementUtils, Filer filer) throws IOException {
          TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
          String factoryClassName = superClassName.getSimpleName() + SUFFIX;
          JavaFileObject jfo = filer.createSourceFile(qualifiedClassName + SUFFIX);
          Writer writer = jfo.openWriter();
          JavaWriter jw = new JavaWriter(writer);
          // 寫包名
          PackageElement pkg = elementUtils.getPackageOf(superClassName);
          if (!pkg.isUnnamed()) {
            jw.emitPackage(pkg.getQualifiedName().toString());
            jw.emitEmptyLine();
          } else {
            jw.emitPackage("");
          }
          jw.beginType(factoryClassName, "class", EnumSet.of(Modifier.PUBLIC));
          jw.emitEmptyLine();
          jw.beginMethod(qualifiedClassName, "create", EnumSet.of(Modifier.PUBLIC), "String", "id");
          jw.beginControlFlow("if (id == null)");
          jw.emitStatement("throw new IllegalArgumentException(\"id is null!\")");
          jw.endControlFlow();
          for (FactoryAnnotatedClass item : itemsMap.values()) {
            jw.beginControlFlow("if (\"%s\".equals(id))", item.getId());
            jw.emitStatement("return new %s()", item.getTypeElement().getQualifiedName().toString());
            jw.endControlFlow();
            jw.emitEmptyLine();
          }
          jw.emitStatement("throw new IllegalArgumentException(\"Unknown id = \" + id)");
          jw.endMethod();
          jw.endType();
          jw.close();
        }
      }

      注意:因?yàn)镴avaWriter非常非常的流行,所以很多處理器、庫(kù)、工具都依賴于JavaWriter。如果你使用依賴管理工具,例如maven或者gradle,假如一個(gè)庫(kù)依賴的JavaWriter的版本比其他的庫(kù)新,這將會(huì)導(dǎo)致一些問(wèn)題。所以我建議你直接拷貝重新打包JavaWiter到你的注解處理器代碼中(實(shí)際它只是一個(gè)Java文件)。

      更新:JavaWrite現(xiàn)在已經(jīng)被JavaPoet取代了。

      處理循環(huán)

      注解處理過(guò)程可能會(huì)多于一次。官方j(luò)avadoc定義處理過(guò)程如下:

      注解處理過(guò)程是一個(gè)有序的循環(huán)過(guò)程。在每次循環(huán)中,一個(gè)處理器可能被要求去處理那些在上一次循環(huán)中產(chǎn)生的源文件和類文件中的注解。第一次循環(huán)的輸入是運(yùn)行此工具的初始輸入。這些初始輸入,可以看成是虛擬的第0此的循環(huán)的輸出。

      一個(gè)簡(jiǎn)單的定義:一個(gè)處理循環(huán)是調(diào)用一個(gè)注解處理器的process()方法。對(duì)應(yīng)到我們的工廠模式的例子中:FactoryProcessor被初始化一次(不是每次循環(huán)都會(huì)新建處理器對(duì)象),然而,如果生成了新的源文件process()能夠被調(diào)用多次。聽(tīng)起來(lái)有點(diǎn)奇怪不是么?原因是這樣的,這些生成的文件中也可能包含@Factory注解,它們還將會(huì)被FactoryProcessor處理。

      例如我們的PizzaStore的例子中將會(huì)經(jīng)過(guò)3次循環(huán)處理:

      Round Input Output
      1 CalzonePizza.java
      Tiramisu.java
      MargheritaPizza.java
      Meal.java
      PizzaStore.java
      MealFactory.java
      2 MealFactory.java — none —
      3 — none — — none —

      我解釋處理循環(huán)還有另外一個(gè)原因。如果你看一下我們的FactoryProcessor代碼你就能注意到,我們收集數(shù)據(jù)和保存它們?cè)谝粋€(gè)私有的域中Map<String, FactoryGroupedClasses> factoryClasses。在第一輪中,我們檢測(cè)到了MagheritaPizza, CalzonePizza和Tiramisu,然后生成了MealFactory.java。在第二輪中把MealFactory作為輸入。因?yàn)樵贛ealFactory中沒(méi)有檢測(cè)到@Factory注解,我們預(yù)期并沒(méi)有錯(cuò)誤,然而我們得到如下的信息:

      1
      Attempt to recreate a file for type com.hannesdorfmann.annotationprocessing101.factory.MealFactory

      這個(gè)問(wèn)題是因?yàn)槲覀儧](méi)有清除factoryClasses,這意味著,在第二輪的process()中,任然保存著第一輪的數(shù)據(jù),并且會(huì)嘗試生成在第一輪中已經(jīng)生成的文件,從而導(dǎo)致這個(gè)錯(cuò)誤的出現(xiàn)。在我們的這個(gè)場(chǎng)景中,我們知道只有在第一輪中檢查@Factory注解的類,所以我們可以簡(jiǎn)單的修復(fù)這個(gè)問(wèn)題,如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 
          try {
            for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
              factoryClass.generateCode(elementUtils, filer);
            }
            // 清除factoryClasses
            factoryClasses.clear();
          } catch (IOException e) {
            error(null, e.getMessage());
          }
          ...
          return true;
      }

      我知道這有其他的方法來(lái)處理這個(gè)問(wèn)題,例如我們也可以設(shè)置一個(gè)布爾值標(biāo)簽等。關(guān)鍵的點(diǎn)是:我們要記住注解處理過(guò)程是需要經(jīng)過(guò)多輪處理的,并且你不能重載或者重新創(chuàng)建已經(jīng)生成的源代碼。

      分離處理器和注解

      如果你已經(jīng)看了我們的代碼庫(kù),你將發(fā)現(xiàn)我們組織我們的代碼到兩個(gè)maven模塊中了。我們這么做是因?yàn)?,我們想讓我們的工廠模式的例子的使用者,在他們的工程中只編譯注解,而包含處理器模塊只是為了編譯。有點(diǎn)暈?我們舉個(gè)例子,如果我們只有一個(gè)包。如果另一個(gè)開(kāi)發(fā)者想要把我們的工廠模式處理器用于他的項(xiàng)目中,他就必須包含@Factory注解和整個(gè)FactoryProcessor的代碼(包括FactoryAnnotatedClass和FactoryGroupedClasses)到他們項(xiàng)目中。我非常確定的是,他并不需要在他已經(jīng)編譯好的項(xiàng)目中包含處理器相關(guān)的代碼。如果你是一個(gè)Android的開(kāi)發(fā)者,你肯定聽(tīng)說(shuō)過(guò)65k個(gè)方法的限制(即在一個(gè).dex文件中,只能尋址65000個(gè)方法)。如果你在FactoryProcessor中使用guava,并且把注解和處理器打包在一個(gè)包中,這樣的話,Android APK安裝包中不只是包含F(xiàn)actoryProcessor的代碼,而也包含了整個(gè)guava的代碼。Guava有大約20000個(gè)方法。所以分開(kāi)注解和處理器是非常有意義的。

      生成的類的實(shí)例化

      你已經(jīng)看到了,在這個(gè)PizzaStore的例子中,生成了MealFactory類,它和其他手寫的Java類沒(méi)有任何區(qū)別。進(jìn)而,你需要就想其他Java對(duì)象,手動(dòng)實(shí)例化它:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public class PizzaStore {
        private MealFactory factory = new MealFactory();
        public Meal order(String mealName) {
          return factory.create(mealName);
        }
        ...
      }

      如果你是一個(gè)Android的開(kāi)發(fā)者,你應(yīng)該也非常熟悉一個(gè)叫做ButterKnife的注解處理器。在ButterKnife中,你使用@InjectView注解Android的View。ButterKnifeProcessor生成一個(gè)MyActivity$$ViewInjector,但是在ButterKnife你不需要手動(dòng)調(diào)用new MyActivity$$ViewInjector()實(shí)例化一個(gè)ButterKnife注入的對(duì)象,而是使用Butterknife.inject(activity)。ButterKnife內(nèi)部使用反射機(jī)制來(lái)實(shí)例化MyActivity$$ViewInjector()對(duì)象:

      1
      2
      3
      try
          Class<?> injector = Class.forName(clsName + "$$ViewInjector");
      } catch (ClassNotFoundException e) { ... }

      但是反射機(jī)制不是很慢么,我們使用注解處理來(lái)生成本地代碼,會(huì)不會(huì)導(dǎo)致很多的反射性能的問(wèn)題?的確,反射機(jī)制的性能確實(shí)是一個(gè)問(wèn)題。然而,它不需要手動(dòng)去創(chuàng)建對(duì)象,確實(shí)提高了開(kāi)發(fā)者的開(kāi)發(fā)速度。ButterKnife中有一個(gè)哈希表HashMap來(lái)緩存實(shí)例化過(guò)的對(duì)象。所以MyActivity$$ViewInjector只是使用反射機(jī)制實(shí)例化一次,第二次需要MyActivity$$ViewInjector的時(shí)候,就直接沖哈希表中獲得。

      FragmentArgs非常類似于ButterKnife。它使用反射機(jī)制來(lái)創(chuàng)建對(duì)象,而不需要開(kāi)發(fā)者手動(dòng)來(lái)做這些。FragmentArgs在處理注解的時(shí)候生成一個(gè)特別的查找表類,它其實(shí)就是一種哈希表,所以整個(gè)FragmentArgs庫(kù)只是在第一次使用的時(shí)候,執(zhí)行一次反射調(diào)用,一旦整個(gè)Class.forName()的Fragemnt的參數(shù)對(duì)象被創(chuàng)建,后面的都是本地代碼運(yùn)行了。

      作為一個(gè)注解注解處理器的開(kāi)發(fā)者,這些都由你來(lái)決定,為其他的注解器使用者,在反射和可用性上找到一個(gè)好的平衡。

      總結(jié)

      到此,我希望你對(duì)注解處理過(guò)程有一個(gè)非常深刻的理解。我必須再次說(shuō)明一下:注解處理器是一個(gè)非常強(qiáng)大的工具,減少了很多無(wú)聊的代碼的編寫。我也想提醒的是,注解處理器可以做到比我上面提到的工廠模式的例子復(fù)雜很多的事情。例如,泛型的類型擦除,因?yàn)樽⒔馓幚砥魇前l(fā)生在類型擦除(type erasure)之前的(譯者注:類型擦除可以參考這里)。就像你所看到的,你在寫注解處理的時(shí)候,有兩個(gè)普遍的問(wèn)題你需要處理:第一問(wèn)題, 如果你想在其他類中使用ElementUtils, TypeUtils和Messager,你就必須把他們作為參數(shù)傳進(jìn)去。在我為Android開(kāi)發(fā)的注解器AnnotatedAdapter中,我嘗試使用Dagger(一個(gè)依賴注入庫(kù))來(lái)解決這個(gè)問(wèn)題。在這個(gè)簡(jiǎn)單的處理中使用它聽(tīng)起來(lái)有點(diǎn)過(guò)頭了,但是它確實(shí)很好用;第二個(gè)問(wèn)題,你必須做查詢Elements的操作。就想我之前提到的,處理Element就解析XML或者HTML一樣。對(duì)于HTML你可以是用jQuery,如果在注解處理器中,有類似于jQuery的庫(kù)那那絕對(duì)是酷斃了。如果你知道有類似的庫(kù),請(qǐng)?jiān)谙旅娴脑u(píng)論告訴我。

      請(qǐng)注意的是,在FactoryProcessor代碼中有一些缺陷和陷阱。這些“錯(cuò)誤”是我故意放進(jìn)去的,是為了演示一些在開(kāi)發(fā)過(guò)程中的常見(jiàn)錯(cuò)誤(例如“Attempt to recreate a file”)。如果你想基于FactoryProcessor寫你自己注解處理器,請(qǐng)不要直接拷貝粘貼這些陷阱過(guò)去,你應(yīng)該從最開(kāi)始就避免它們。

      我在后續(xù)的博客中將會(huì)寫注解處理器的單元測(cè)試,敬請(qǐng)關(guān)注。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多