精华内容
参与话题
问答
  • 文章转自:http://www.race604.com/annotation-processing/在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor)。在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么...

    文章转自:http://www.race604.com/annotation-processing/


    在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor)。在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么;然后,我将一步一步实现一个简单的注解器。

    一些基本概念

    在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。

    注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解。如果你不熟悉注解,你可以在这官方文档中得到更多信息。注解处理器在Java 5开始就有了,但是从Java 6(2006年12月发布)开始才有可用的API。过了一些时间,Java世界才意识到注解处理器的强大作用,所以它到最近几年才流行起来。

    一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这具体的含义什么呢?你可以生成Java代码!这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

    虚处理器AbstractProcessor

    我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:

    [java] view plain copy
    1. package com.example;  
    2.   
    3. public class MyProcessor extends AbstractProcessor {  
    4.   
    5.     @Override  
    6.     public synchronized void init(ProcessingEnvironment env){ }  
    7.   
    8.     @Override  
    9.     public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }  
    10.   
    11.     @Override  
    12.     public Set<String> getSupportedAnnotationTypes() { }  
    13.   
    14.     @Override  
    15.     public SourceVersion getSupportedSourceVersion() { }  
    16.   
    17. }  

    • init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。后面我们将看到详细的内容。
    • process(Set<? extends TypeElement> annotations, RoundEnvironment env) : 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
    • getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
    • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者。

    在Java 7中,你也可以使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion(),像这样:

    [java] view plain copy
    1. @SupportedSourceVersion(SourceVersion.latestSupported())  
    2. @SupportedAnnotationTypes({  
    3.    // 合法注解全名的集合  
    4.  })  
    5. public class MyProcessor extends AbstractProcessor {  
    6.   
    7.     @Override  
    8.     public synchronized void init(ProcessingEnvironment env){ }  
    9.   
    10.     @Override  
    11.     public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }  
    12. }  
    因为兼容的原因,特别是针对Android平台,我建议使用重载getSupportedAnnotationTypes()和getSupportedSourceVersion()方法代替@SupportedAnnotationTypes和@SuppozrtedSourceVersion

    接下来的你必须知道的事情是,注解处理器是运行它自己的虚拟机JVM中。是的,你没有看错,javac启动一个完整Java虚拟机来运行注解处理器。这对你意味着什么?你可以使用任何你在其他java应用中使用的的东西。使用guava。如果你愿意,你可以使用依赖注入工具,例如dagger或者其他你想要的类库。但是不要忘记,即使是一个很小的处理,你也要像其他Java应用一样,注意算法效率,以及设计模式。

    注册你的处理器

    你可能会问,我怎样将处理器MyProcessor注册到javac中。你必须提供一个.jar文件。就像其他.jar文件一样,你打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件javax.annotation.processing.ProcessorMETA-INF/services路径下。所以,你的.jar文件看起来就像下面这样:

    • MyProcessor.jar
    • -- com
    • -- -- example
    • -- -- -- MyProcessor.class
    • -- META-INF
    • -- -- services
    • -- --- javax.annotation.processing.Processor

    打包进MyProcessor.jar中的javax.annotation.processing.Processor的内容是,注解处理器的合法的全名列表,每一个元素换行分割:

    [java] view plain copy
    1. com.example.MyProcessor    
    2. com.foo.OtherProcessor    
    3. net.blabla.SpecialProcessor    

    MyProcessor.jar放到你的builpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器。

    例子:工厂模式

    是时候来说一个实际的例子了。我们将使用maven工具来作为我们的编译系统和依赖管理工具。如果你不熟悉maven,不用担心,因为maven不是必须的。本例子的完成代码在Github上。
    开始之前,我必须说,要为这个教程找到一个需要用注解处理器解决的简单问题,实在并不容易。这里我们将实现一个非常简单的工厂模式(不是抽象工厂模式)。这将对注解处理器的API做一个非常简明的介绍。所以,这个问题的程序并不是那么有用,也不是一个真实世界的例子。所以在此申明,你将学习关于注解处理过程的相关内容,而不是设计模式。
    我们将要解决的问题是:我们将实现一个披萨店,这个披萨店给消费者提供两种披萨(“Margherita”“Calzone”)以及提拉米苏甜点(Tiramisu)。
    看一下如下的代码,不需要做任何更多的解释:

    [java] view plain copy
    1. public interface Meal {    
    2.   public float getPrice();  
    3. }  
    4.   
    5. public class MargheritaPizza implements Meal {  
    6.   
    7.   @Override public float getPrice() {  
    8.     return 6.0f;  
    9.   }  
    10. }  
    11.   
    12. public class CalzonePizza implements Meal {  
    13.   
    14.   @Override public float getPrice() {  
    15.     return 8.5f;  
    16.   }  
    17. }  
    18.   
    19. public class Tiramisu implements Meal {  
    20.   
    21.   @Override public float getPrice() {  
    22.     return 4.5f;  
    23.   }  
    24. }  

    为了在我们的披萨店PizzsStore下订单,消费者需要输入餐(Meal)的名字。

    [java] view plain copy
    1. public class PizzaStore {  
    2.   
    3.   public Meal order(String mealName) {  
    4.   
    5.     if (mealName == null) {  
    6.       throw new IllegalArgumentException("Name of the meal is null!");  
    7.     }  
    8.   
    9.     if ("Margherita".equals(mealName)) {  
    10.       return new MargheritaPizza();  
    11.     }  
    12.   
    13.     if ("Calzone".equals(mealName)) {  
    14.       return new CalzonePizza();  
    15.     }  
    16.   
    17.     if ("Tiramisu".equals(mealName)) {  
    18.       return new Tiramisu();  
    19.     }  
    20.   
    21.     throw new IllegalArgumentException("Unknown meal '" + mealName + "'");  
    22.   }  
    23.   
    24.   public static void main(String[] args) throws IOException {  
    25.     PizzaStore pizzaStore = new PizzaStore();  
    26.     Meal meal = pizzaStore.order(readConsole());  
    27.     System.out.println("Bill: $" + meal.getPrice());  
    28.   }  
    29. }  
    正如你所见,在order()方法中,我们有很多的if语句,并且如果我们每添加一种新的披萨,我们都要添加一条新的if语句。但是等一下,使用注解处理和工厂模式,我们可以让注解处理器来帮我们自动生成这些if语句。如此以来,我们期望的是如下的代码:
    [java] view plain copy
    1. public class PizzaStore {  
    2.   
    3.   private MealFactory factory = new MealFactory();  
    4.   
    5.   public Meal order(String mealName) {  
    6.     return factory.create(mealName);  
    7.   }  
    8.   
    9.   public static void main(String[] args) throws IOException {  
    10.     PizzaStore pizzaStore = new PizzaStore();  
    11.     Meal meal = pizzaStore.order(readConsole());  
    12.     System.out.println("Bill: $" + meal.getPrice());  
    13.   }  
    14. }  
    MealFactory应该是如下的样子:
    [java] view plain copy
    1. public class MealFactory {  
    2.   
    3.   public Meal create(String id) {  
    4.     if (id == null) {  
    5.       throw new IllegalArgumentException("id is null!");  
    6.     }  
    7.     if ("Calzone".equals(id)) {  
    8.       return new CalzonePizza();  
    9.     }  
    10.   
    11.     if ("Tiramisu".equals(id)) {  
    12.       return new Tiramisu();  
    13.     }  
    14.   
    15.     if ("Margherita".equals(id)) {  
    16.       return new MargheritaPizza();  
    17.     }  
    18.   
    19.     throw new IllegalArgumentException("Unknown id = " + id);  
    20.   }  
    21. }  

    @Factory注解

    你能猜到么:我们想用注解处理器自动生成MealFactory。更一般的说,我们将想要提供一个注解和一个处理器来生成工厂类。

    我们先来看一下@Factory注解:

    [java] view plain copy
    1. @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS)  
    2. public @interface Factory {  
    3.   
    4.   /** 
    5.    * 工厂的名字 
    6.    */  
    7.   Class type();  
    8.   
    9.   /** 
    10.    * 用来表示生成哪个对象的唯一id 
    11.    */  
    12.   String id();  
    13. }  
    想法是这样的:我们将使用同样的type()注解那些属于同一个工厂的类,并且用注解的id()做一个映射,例如从"Calzone"映射到"ClzonePizza"类。我们应用@Factory注解到我们的类中,如下:
    [java] view plain copy
    1. @Factory(  
    2.     id = "Margherita",  
    3.     type = Meal.class  
    4. )  
    5. public class MargheritaPizza implements Meal {  
    6.   
    7.   @Override public float getPrice() {  
    8.     return 6f;  
    9.   }  
    10. }  
    [java] view plain copy
    1. @Factory(  
    2.     id = "Calzone",  
    3.     type = Meal.class  
    4. )  
    5. public class CalzonePizza implements Meal {  
    6.   
    7.   @Override public float getPrice() {  
    8.     return 8.5f;  
    9.   }  
    10. }  
    [java] view plain copy
    1. @Factory(  
    2.     id = "Tiramisu",  
    3.     type = Meal.class  
    4. )  
    5. public class Tiramisu implements Meal {  
    6.   
    7.   @Override public float getPrice() {  
    8.     return 4.5f;  
    9.   }  
    10. }  
    你可能会问你自己,我们是否可以只把@Factory注解应用到我们的Meal接口上?答案是,注解是不能继承的。一个类class X被注解,并不意味着它的子类class Y extends X会自动被注解。在我们开始写处理器的代码之前,我们先规定如下一些规则:
    1. 只有类可以被@Factory注解,因为接口或者抽象类并不能用new操作实例化;
    2. 被@Factory注解的类,必须至少提供一个公开的默认构造器(即没有参数的构造函数)。否者我们没法实例化一个对象。
    3. 被@Factory注解的类必须直接或者间接的继承于type()指定的类型;
    4. 具有相同的type的注解类,将被聚合在一起生成一个工厂类。这个生成的类使用Factory后缀,例如type = Meal.class,将生成MealFactory工厂类;
    5. id只能是String类型,并且在同一个type组中必须唯一。

    处理器

    我将通过添加代码和一段解释的方法,一步一步的引导你来构建我们的处理器。省略号(...)表示省略那些已经讨论过的或者将在后面的步骤中讨论的代码,目的是为了让我们的代码有更好的可读性。正如我们前面说的,我们完整的代码可以在Github上找到。好了,让我们来看一下我们的处理器类FactoryProcessor的骨架:

    [java] view plain copy
    1. @AutoService(Processor.class)  
    2. public class FactoryProcessor extends AbstractProcessor {  
    3.   
    4.   private Types typeUtils;  
    5.   private Elements elementUtils;  
    6.   private Filer filer;  
    7.   private Messager messager;  
    8.   private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();  
    9.   
    10.   @Override  
    11.   public synchronized void init(ProcessingEnvironment processingEnv) {  
    12.     super.init(processingEnv);  
    13.     typeUtils = processingEnv.getTypeUtils();  
    14.     elementUtils = processingEnv.getElementUtils();  
    15.     filer = processingEnv.getFiler();  
    16.     messager = processingEnv.getMessager();  
    17.   }  
    18.   
    19.   @Override  
    20.   public Set<String> getSupportedAnnotationTypes() {  
    21.     Set<String> annotataions = new LinkedHashSet<String>();  
    22.     annotataions.add(Factory.class.getCanonicalName());  
    23.     return annotataions;  
    24.   }  
    25.   
    26.   @Override  
    27.   public SourceVersion getSupportedSourceVersion() {  
    28.     return SourceVersion.latestSupported();  
    29.   }  
    30.   
    31.   @Override  
    32.   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    33.     ...  
    34.   }  
    35. }  
    你看到在代码的第一行是@AutoService(Processor.class),这是什么?这是一个其他注解处理器中引入的注解。AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。是的,你没有看错,我们可以在注解处理器中使用注解。非常方便,难道不是么?在getSupportedAnnotationTypes()中,我们指定本处理器将处理@Factory注解。

    Elements和TypeMirrors

    在init()中我们获得如下引用:

    • Elements:一个用来处理Element的工具类(后面将做详细说明);
    • Types:一个用来处理TypeMirror的工具类(后面将做详细说明);
    • Filer:正如这个名字所示,使用Filer你可以创建文件。
    在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个:

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

    你必须换个角度来看源代码,它只是结构化的文本,他不是可运行的。你可以想象它就像你将要去解析的XML文件一样(或者是编译器中抽象的语法树)。就像XML解释器一样,有一些类似DOM的元素。你可以从一个元素导航到它的父或者子元素上。

    举例来说,假如你有一个代表public class Foo类的TypeElement元素,你可以遍历它的孩子,如下:

    [java] view plain copy
    1. TypeElement fooClass = ... ;    
    2. for (Element e : fooClass.getEnclosedElements()){ // iterate over children    
    3.     Element parent = e.getEnclosingElement();  // parent == fooClass  
    4. }  
    正如你所见,Element代表的是源代码。TypeElement代表的是源代码中的类型元素,例如类。然而,TypeElement并不包含类本身的信息。你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror。

    搜索@Factory注解

    我们来一步一步实现process()方法。首先,我们从搜索被注解了@Factory的类开始:

    [java] view plain copy
    1. @AutoService(Processor.class)  
    2. public class FactoryProcessor extends AbstractProcessor {  
    3.   
    4.   private Types typeUtils;  
    5.   private Elements elementUtils;  
    6.   private Filer filer;  
    7.   private Messager messager;  
    8.   private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<String, FactoryGroupedClasses>();  
    9.     ...  
    10.   
    11.   @Override  
    12.   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    13.   
    14.     // 遍历所有被注解了@Factory的元素  
    15.     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {  
    16.           ...  
    17.     }  
    18.   }  
    19.  ...  
    20. }  

    这里并没有什么高深的技术。roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。你可能已经注意到,我们并没有说“所有被注解了@Factory的类的列表”,因为它真的是返回Element的列表。请记住:Element可以是类、方法、变量等。所以,接下来,我们必须检查这些Element是否是一个类:

    [java] view plain copy
    1. @Override  
    2.   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    3.   
    4.     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {  
    5.   
    6.       // 检查被注解为@Factory的元素是否是一个类  
    7.       if (annotatedElement.getKind() != ElementKind.CLASS) {  
    8.             ...  
    9.       }  
    10.    }  
    11.    ...  
    12. }  
    为什么要这么做?我们要确保只有class元素被我们的处理器处理。前面我们已经学习到类是用TypeElement表示。我们为什么不这样判断呢if(!(annotatedElement instanceof TypeElement))?这是错误的,因为接口(interface)类型也是TypeElement。所以在注解处理器中,我们要避免使用instanceof,而是配合TypeMirror使用EmentKind或者TypeKind。

    错误处理

    在init()中,我们也获得了一个Messager对象的引用。Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。在官方文档中描述了消息的不同级别。非常重要的是Kind.ERROR,因为这种类型的信息用来表示我们的注解处理器处理失败了。很有可能是第三方开发者错误的使用了@Factory注解(例如,给接口使用了@Factory注解)。这个概念和传统的Java应用有点不一样,在传统Java应用中我们可能就抛出一个异常Exception。如果你在process()中抛出一个异常,那么运行注解处理器的JVM将会崩溃(就像其他Java应用一样),使用我们注解处理器FactoryProcessor第三方开发者将会从javac中得到非常难懂的出错信息,因为它包含FactoryProcessor的堆栈跟踪(Stacktace)信息。因此,注解处理器就有一个Messager类,它能够打印非常优美的错误信息。除此之外,你还可以连接到出错的元素。在像IntelliJ这种现代的IDE(集成开发环境)中,第三方开发者可以直接点击错误信息,IDE将会直接跳转到第三方开发者项目的出错的源文件的相应的行。

    我们重新回到process()方法的实现。如果遇到一个非类类型被注解@Factory,我们发出一个出错信息:

    [java] view plain copy
    1. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    2.   
    3.     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {  
    4.   
    5.       // 检查被注解为@Factory的元素是否是一个类  
    6.       if (annotatedElement.getKind() != ElementKind.CLASS) {  
    7.         error(annotatedElement, "Only classes can be annotated with @%s",  
    8.             Factory.class.getSimpleName());  
    9.         return true// 退出处理  
    10.       }  
    11.       ...  
    12.     }  
    13.   
    14. private void error(Element e, String msg, Object... args) {    
    15.     messager.printMessage(  
    16.         Diagnostic.Kind.ERROR,  
    17.         String.format(msg, args),  
    18.         e);  
    19.   }  
    20.   
    21. }  
    让Messager显示相关出错信息,更重要的是注解处理器程序必须完成运行而不崩溃,这就是为什么在调用error()后直接return。如果我们不直接返回,process()将继续运行,因为messager.printMessage( Diagnostic.Kind.ERROR)不会停止此进程。因此,如果我们在打印错误信息以后不返回的话,我们很可能就会运行到一个NullPointerException等。就像我们前面说的,如果我们继续运行process(),问题是如果在process()中抛出一个未处理的异常,javac将会打印出内部的NullPointerException,而不是Messager中的错误信息。

    数据模型

    在继续检查被注解@Fractory的类是否满足我们上面说的5条规则之前,我们将介绍一个让我们更方便继续处理的数据结构。有时候,一个问题或者解释器看起来如此简单,以至于程序员倾向于用一个面向过程方式来写整个处理器。但是你知道吗?一个注解处理器任然是一个Java程序,所以我们需要使用面向对象编程、接口、设计模式,以及任何你将在其他普通Java程序中使用的技巧。

    我们的FactoryProcessor非常简单,但是我们仍然想要把一些信息存为对象。在FactoryAnnotatedClass中,我们保存被注解类的数据,比如合法的类的名字,以及@Factory注解本身的一些信息。所以,我们保存TypeElement和处理过的@Factory注解:

    [java] view plain copy
    1. public class FactoryAnnotatedClass {  
    2.   
    3.   private TypeElement annotatedClassElement;  
    4.   private String qualifiedSuperClassName;  
    5.   private String simpleTypeName;  
    6.   private String id;  
    7.   
    8.   public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException {  
    9.     this.annotatedClassElement = classElement;  
    10.     Factory annotation = classElement.getAnnotation(Factory.class);  
    11.     id = annotation.id();  
    12.   
    13.     if (StringUtils.isEmpty(id)) {  
    14.       throw new IllegalArgumentException(  
    15.           String.format("id() in @%s for class %s is null or empty! that's not allowed",  
    16.               Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));  
    17.     }  
    18.   
    19.     // Get the full QualifiedTypeName  
    20.     try {  
    21.       Class<?> clazz = annotation.type();  
    22.       qualifiedSuperClassName = clazz.getCanonicalName();  
    23.       simpleTypeName = clazz.getSimpleName();  
    24.     } catch (MirroredTypeException mte) {  
    25.       DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();  
    26.       TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();  
    27.       qualifiedSuperClassName = classTypeElement.getQualifiedName().toString();  
    28.       simpleTypeName = classTypeElement.getSimpleName().toString();  
    29.     }  
    30.   }  
    31.   
    32.   /** 
    33.    * 获取在{@link Factory#id()}中指定的id 
    34.    * return the id 
    35.    */  
    36.   public String getId() {  
    37.     return id;  
    38.   }  
    39.   
    40.   /** 
    41.    * 获取在{@link Factory#type()}指定的类型合法全名 
    42.    * 
    43.    * @return qualified name 
    44.    */  
    45.   public String getQualifiedFactoryGroupName() {  
    46.     return qualifiedSuperClassName;  
    47.   }  
    48.   
    49.   
    50.   /** 
    51.    * 获取在{@link Factory#type()}{@link Factory#type()}指定的类型的简单名字 
    52.    * 
    53.    * @return qualified name 
    54.    */  
    55.   public String getSimpleFactoryGroupName() {  
    56.     return simpleTypeName;  
    57.   }  
    58.   
    59.   /** 
    60.    * 获取被@Factory注解的原始元素 
    61.    */  
    62.   public TypeElement getTypeElement() {  
    63.     return annotatedClassElement;  
    64.   }  
    65. }  
    代码很多,但是最重要的部分是在构造函数中。其中你能找到如下的代码:

    [java] view plain copy
    1. Factory annotation = classElement.getAnnotation(Factory.class);    
    2. id = annotation.id(); // Read the id value (like "Calzone" or "Tiramisu")  
    3.   
    4. if (StringUtils.isEmpty(id)) {    
    5.     throw new IllegalArgumentException(  
    6.           String.format("id() in @%s for class %s is null or empty! that's not allowed",  
    7.               Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));  
    8. }  
    这里我们获取@Factory注解,并且检查id是否为空?如果为空,我们将抛出IllegalArgumentException异常。你可能感到疑惑的是,前面我们说了不要抛出异常,而是使用Messager。这里仍然不矛盾。我们抛出内部的异常,你在将在后面看到会在process()中捕获这个异常。我这样做出于一下两个原因:
    1. 我想示意我们应该像普通的Java程序一样编码。抛出和捕获异常是非常好的Java编程实践;
    2. 如果我们想要在FactoryAnnotatedClass中打印信息,我需要也传入Messager对象,并且我们在错误处理一节中已经提到,为了打印Messager信息,我们必须成功停止处理器运行。如果我们使用Messager打印了错误信息,我们怎样告知process()出现了错误呢?最容易,并且我认为最直观的方式就是抛出一个异常,然后让process()捕获之。
    接下来,我们将获取@Fractory注解中的type成员。我们比较关心的是合法的全名:

    [java] view plain copy
    1. try {    
    2.       Class<?> clazz = annotation.type();  
    3.       qualifiedGroupClassName = clazz.getCanonicalName();  
    4.       simpleFactoryGroupName = clazz.getSimpleName();  
    5. catch (MirroredTypeException mte) {  
    6.       DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();  
    7.       TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();  
    8.       qualifiedGroupClassName = classTypeElement.getQualifiedName().toString();  
    9.       simpleFactoryGroupName = classTypeElement.getSimpleName().toString();  
    10. }  
    这里有一点小麻烦,因为这里的类型是一个java.lang.Class。这就意味着,他是一个真正的Class对象。因为注解处理是在编译Java源代码之前。我们需要考虑如下两种情况:
    1. 这个类已经被编译:这种情况是:如果第三方.jar包含已编译的被@Factory注解.class文件。在这种情况下,我们可以想try中那块代码中所示直接获取Class。
    2. 这个还没有被编译:这种情况是我们尝试编译被@Fractory注解的源代码。这种情况下,直接获取Class会抛出MirroredTypeException异常。幸运的是,MirroredTypeException包含一个TypeMirror,它表示我们未编译类。因为我们已经知道它必定是一个类类型(我们已经在前面检查过),我们可以直接强制转换为DeclaredType,然后读取TypeElement来获取合法的名字。
    好了,我们现在还需要一个数据结构FactoryGroupedClasses,它将简单的组合所有的FactoryAnnotatedClasses到一起。

    [java] view plain copy
    1. public class FactoryGroupedClasses {  
    2.   
    3.   private String qualifiedClassName;  
    4.   
    5.   private Map<String, FactoryAnnotatedClass> itemsMap =  
    6.       new LinkedHashMap<String, FactoryAnnotatedClass>();  
    7.   
    8.   public FactoryGroupedClasses(String qualifiedClassName) {  
    9.     this.qualifiedClassName = qualifiedClassName;  
    10.   }  
    11.   
    12.   public void add(FactoryAnnotatedClass toInsert) throws IdAlreadyUsedException {  
    13.   
    14.     FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId());  
    15.     if (existing != null) {  
    16.       throw new IdAlreadyUsedException(existing);  
    17.     }  
    18.   
    19.     itemsMap.put(toInsert.getId(), toInsert);  
    20.   }  
    21.   
    22.   public void generateCode(Elements elementUtils, Filer filer) throws IOException {  
    23.     ...  
    24.   }  
    25. }  
    正如你所见,这是一个基本的Map<String, FactoryAnnotatedClass>,这个映射表用来映射@Factory.id()到FactoryAnnotatedClass。我们选择Map这个数据类型,是因为我们要确保每个id是唯一的,我们可以很容易通过map查找实现。generateCode()方法将被用来生成工厂类代码(将在后面讨论)。

    匹配标准

    我们继续实现process()方法。接下来我们想要检查被注解的类必须有只要一个公开的构造函数,不是抽象类,继承于特定的类型,以及是一个公开类:

    [java] view plain copy
    1. public class FactoryProcessor extends AbstractProcessor {  
    2.   
    3.   @Override  
    4.   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    5.   
    6.     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {  
    7.   
    8.       ...  
    9.   
    10.       // 因为我们已经知道它是ElementKind.CLASS类型,所以可以直接强制转换  
    11.       TypeElement typeElement = (TypeElement) annotatedElement;  
    12.   
    13.       try {  
    14.         FactoryAnnotatedClass annotatedClass =  
    15.             new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException  
    16.   
    17.         if (!isValidClass(annotatedClass)) {  
    18.           return true// 已经打印了错误信息,退出处理过程  
    19.          }  
    20.        } catch (IllegalArgumentException e) {  
    21.         // @Factory.id()为空  
    22.         error(typeElement, e.getMessage());  
    23.         return true;  
    24.        }  
    25.           ...  
    26.    }  
    27.   
    28.  private boolean isValidClass(FactoryAnnotatedClass item) {  
    29.   
    30.     // 转换为TypeElement, 含有更多特定的方法  
    31.     TypeElement classElement = item.getTypeElement();  
    32.   
    33.     if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {  
    34.       error(classElement, "The class %s is not public.",  
    35.           classElement.getQualifiedName().toString());  
    36.       return false;  
    37.     }  
    38.   
    39.     // 检查是否是一个抽象类  
    40.     if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {  
    41.       error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%",  
    42.           classElement.getQualifiedName().toString(), Factory.class.getSimpleName());  
    43.       return false;  
    44.     }  
    45.   
    46.     // 检查继承关系: 必须是@Factory.type()指定的类型子类  
    47.     TypeElement superClassElement =  
    48.         elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());  
    49.     if (superClassElement.getKind() == ElementKind.INTERFACE) {  
    50.       // 检查接口是否实现了                                       if(!classElement.getInterfaces().contains(superClassElement.asType())) {  
    51.         error(classElement, "The class %s annotated with @%s must implement the interface %s",  
    52.             classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),  
    53.             item.getQualifiedFactoryGroupName());  
    54.         return false;  
    55.       }  
    56.     } else {  
    57.       // 检查子类  
    58.       TypeElement currentClass = classElement;  
    59.       while (true) {  
    60.         TypeMirror superClassType = currentClass.getSuperclass();  
    61.   
    62.         if (superClassType.getKind() == TypeKind.NONE) {  
    63.           // 到达了基本类型(java.lang.Object), 所以退出  
    64.           error(classElement, "The class %s annotated with @%s must inherit from %s",  
    65.               classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),  
    66.               item.getQualifiedFactoryGroupName());  
    67.           return false;  
    68.         }  
    69.   
    70.         if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {  
    71.           // 找到了要求的父类  
    72.           break;  
    73.         }  
    74.   
    75.         // 在继承树上继续向上搜寻  
    76.         currentClass = (TypeElement) typeUtils.asElement(superClassType);  
    77.       }  
    78.     }  
    79.   
    80.     // 检查是否提供了默认公开构造函数  
    81.     for (Element enclosed : classElement.getEnclosedElements()) {  
    82.       if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {  
    83.         ExecutableElement constructorElement = (ExecutableElement) enclosed;  
    84.         if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()  
    85.             .contains(Modifier.PUBLIC)) {  
    86.           // 找到了默认构造函数  
    87.           return true;  
    88.         }  
    89.       }  
    90.     }  
    91.   
    92.     // 没有找到默认构造函数  
    93.     error(classElement, "The class %s must provide an public empty default constructor",  
    94.         classElement.getQualifiedName().toString());  
    95.     return false;  
    96.   }  
    97. }  
    我们这里添加了isValidClass()方法,来检查是否我们所有的规则都被满足了:

    • 必须是公开类:classElement.getModifiers().contains(Modifier.PUBLIC)
    • 必须是非抽象类:classElement.getModifiers().contains(Modifier.ABSTRACT)
    • 必须是@Factoy.type()指定的类型的子类或者接口的实现:首先我们使用elementUtils.getTypeElement(item.getQualifiedFactoryGroupName())创建一个传入的Class(@Factoy.type())的元素。是的,你可以仅仅通过已知的合法类名来直接创建TypeElement(使用TypeMirror)。接下来我们检查它是一个接口还是一个类:superClassElement.getKind() == ElementKind.INTERFACE。所以我们这里有两种情况:如果是接口,就判断classElement.getInterfaces().contains(superClassElement.asType());如果是类,我们就必须使用currentClass.getSuperclass()扫描继承层级。注意,整个检查也可以使用typeUtils.isSubtype()来实现
    • 类必须有一个公开的默认构造函数:我们遍历所有的闭元素classElement.getEnclosedElements(),然后检查ElementKind.CONSTRUCTOR、Modifier.PUBLIC以及constructorElement.getParameters().size() == 0
    如果所有这些条件都满足,isValidClass()返回true,否者就打印错误信息,并且返回false。

    组合被注解的类

    一旦我们检查isValidClass()成功,我们将添加FactoryAnnotatedClass到对应的FactoryGroupedClasses中,如下:

    [java] view plain copy
    1. public class FactoryProcessor extends AbstractProcessor {  
    2.   
    3.    private Map<String, FactoryGroupedClasses> factoryClasses =  
    4.       new LinkedHashMap<String, FactoryGroupedClasses>();  
    5.   
    6.   
    7.  @Override  
    8.   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    9.       ...  
    10.       try {  
    11.         FactoryAnnotatedClass annotatedClass =  
    12.             new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException  
    13.   
    14.           if (!isValidClass(annotatedClass)) {  
    15.           return true// 错误信息被打印,退出处理流程  
    16.         }  
    17.   
    18.         // 所有检查都没有问题,所以可以添加了  
    19.         FactoryGroupedClasses factoryClass =  
    20.         factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());  
    21.         if (factoryClass == null) {  
    22.           String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();  
    23.           factoryClass = new FactoryGroupedClasses(qualifiedGroupName);  
    24.           factoryClasses.put(qualifiedGroupName, factoryClass);  
    25.         }  
    26.   
    27.         // 如果和其他的@Factory标注的类的id相同冲突,  
    28.         // 抛出IdAlreadyUsedException异常  
    29.         factoryClass.add(annotatedClass);  
    30.       } catch (IllegalArgumentException e) {  
    31.         // @Factory.id()为空 --> 打印错误信息  
    32.         error(typeElement, e.getMessage());  
    33.         return true;  
    34.       } catch (IdAlreadyUsedException e) {  
    35.         FactoryAnnotatedClass existing = e.getExisting();  
    36.         // 已经存在  
    37.         error(annotatedElement,  
    38.             "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id",  
    39.             typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(),  
    40.             existing.getTypeElement().getQualifiedName().toString());  
    41.         return true;  
    42.       }  
    43.     }  
    44.     ...  
    45. }  

    代码生成

    我们已经收集了所有的被@Factory注解的类保存到为FactoryAnnotatedClass,并且组合到了FactoryGroupedClasses。现在我们将为每个工厂生成Java文件了:

    [java] view plain copy
    1. @Override  
    2. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {    
    3.     ...  
    4.   try {  
    5.         for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {  
    6.           factoryClass.generateCode(elementUtils, filer);  
    7.         }  
    8.     } catch (IOException e) {  
    9.         error(null, e.getMessage());  
    10.     }  
    11.   
    12.     return true;  
    13. }  
    写Java文件,和写其他普通文件没有什么两样。使用Filer提供的Writer对象,我们可以连接字符串来写我们生成的Java代码。幸运的是,Square公司(因为提供了许多非常优秀的开源项目二非常有名)给我们提供了JavaWriter,这是一个高级的生成Java代码的库:

    [java] view plain copy
    1. public class FactoryGroupedClasses {  
    2.   
    3.   /** 
    4.    * 将被添加到生成的工厂类的名字中 
    5.    */  
    6.   private static final String SUFFIX = "Factory";  
    7.   
    8.   private String qualifiedClassName;  
    9.   
    10.   private Map<String, FactoryAnnotatedClass> itemsMap =  
    11.       new LinkedHashMap<String, FactoryAnnotatedClass>();  
    12.     ...  
    13.   
    14.   public void generateCode(Elements elementUtils, Filer filer) throws IOException {  
    15.   
    16.     TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);  
    17.     String factoryClassName = superClassName.getSimpleName() + SUFFIX;  
    18.   
    19.     JavaFileObject jfo = filer.createSourceFile(qualifiedClassName + SUFFIX);  
    20.     Writer writer = jfo.openWriter();  
    21.     JavaWriter jw = new JavaWriter(writer);  
    22.   
    23.     // 写包名  
    24.     PackageElement pkg = elementUtils.getPackageOf(superClassName);  
    25.     if (!pkg.isUnnamed()) {  
    26.       jw.emitPackage(pkg.getQualifiedName().toString());  
    27.       jw.emitEmptyLine();  
    28.     } else {  
    29.       jw.emitPackage("");  
    30.     }  
    31.   
    32.     jw.beginType(factoryClassName, "class", EnumSet.of(Modifier.PUBLIC));  
    33.     jw.emitEmptyLine();  
    34.     jw.beginMethod(qualifiedClassName, "create", EnumSet.of(Modifier.PUBLIC), "String""id");  
    35.   
    36.     jw.beginControlFlow("if (id == null)");  
    37.     jw.emitStatement("throw new IllegalArgumentException(\"id is null!\")");  
    38.     jw.endControlFlow();  
    39.   
    40.     for (FactoryAnnotatedClass item : itemsMap.values()) {  
    41.       jw.beginControlFlow("if (\"%s\".equals(id))", item.getId());  
    42.       jw.emitStatement("return new %s()", item.getTypeElement().getQualifiedName().toString());  
    43.       jw.endControlFlow();  
    44.       jw.emitEmptyLine();  
    45.     }  
    46.   
    47.     jw.emitStatement("throw new IllegalArgumentException(\"Unknown id = \" + id)");  
    48.     jw.endMethod();  
    49.     jw.endType();  
    50.     jw.close();  
    51.   }  
    52. }  
    注意:因为JavaWriter非常非常的流行,所以很多处理器、库、工具都依赖于JavaWriter。如果你使用依赖管理工具,例如maven或者gradle,假如一个库依赖的JavaWriter的版本比其他的库新,这将会导致一些问题。所以我建议你直接拷贝重新打包JavaWiter到你的注解处理器代码中(实际它只是一个Java文件)。

    更新:JavaWrite现在已经被JavaPoet取代了。

    处理循环

    注解处理过程可能会多于一次。官方javadoc定义处理过程如下:

    注解处理过程是一个有序的循环过程。在每次循环中,一个处理器可能被要求去处理那些在上一次循环中产生的源文件和类文件中的注解。第一次循环的输入是运行此工具的初始输入。这些初始输入,可以看成是虚拟的第0此的循环的输出。

    一个简单的定义:一个处理循环是调用一个注解处理器的process()方法。对应到我们的工厂模式的例子中:FactoryProcessor被初始化一次(不是每次循环都会新建处理器对象),然而,如果生成了新的源文件process()能够被调用多次。听起来有点奇怪不是么?原因是这样的,这些生成的文件中也可能包含@Factory注解,它们还将会被FactoryProcessor处理。

    例如我们的PizzaStore的例子中将会经过3次循环处理:

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

    我解释处理循环还有另外一个原因。如果你看一下我们的FactoryProcessor代码你就能注意到,我们收集数据和保存它们在一个私有的域中Map<String, FactoryGroupedClasses> factoryClasses。在第一轮中,我们检测到了MagheritaPizza, CalzonePizza和Tiramisu,然后生成了MealFactory.java。在第二轮中把MealFactory作为输入。因为在MealFactory中没有检测到@Factory注解,我们预期并没有错误,然而我们得到如下的信息:

    [java] view plain copy
    1. Attempt to recreate a file for type com.hannesdorfmann.annotationprocessing101.factory.MealFactory    
    这个问题是因为我们没有清除factoryClasses,这意味着,在第二轮的process()中,任然保存着第一轮的数据,并且会尝试生成在第一轮中已经生成的文件,从而导致这个错误的出现。在我们的这个场景中,我们知道只有在第一轮中检查@Factory注解的类,所以我们可以简单的修复这个问题,如下:

    [java] view plain copy
    1. @Override  
    2. public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {    
    3.     try {  
    4.       for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {  
    5.         factoryClass.generateCode(elementUtils, filer);  
    6.       }  
    7.   
    8.       // 清除factoryClasses  
    9.       factoryClasses.clear();  
    10.   
    11.     } catch (IOException e) {  
    12.       error(null, e.getMessage());  
    13.     }  
    14.     ...  
    15.     return true;  
    16. }  
    我知道这有其他的方法来处理这个问题,例如我们也可以设置一个布尔值标签等。关键的点是:我们要记住注解处理过程是需要经过多轮处理的,并且你不能重载或者重新创建已经生成的源代码。

    分离处理器和注解

    如果你已经看了我们的代码库,你将发现我们组织我们的代码到两个maven模块中了。我们这么做是因为,我们想让我们的工厂模式的例子的使用者,在他们的工程中只编译注解,而包含处理器模块只是为了编译。有点晕?我们举个例子,如果我们只有一个包。如果另一个开发者想要把我们的工厂模式处理器用于他的项目中,他就必须包含@Factory注解和整个FactoryProcessor的代码(包括FactoryAnnotatedClass和FactoryGroupedClasses)到他们项目中。我非常确定的是,他并不需要在他已经编译好的项目中包含处理器相关的代码。如果你是一个Android的开发者,你肯定听说过65k个方法的限制(即在一个.dex文件中,只能寻址65000个方法)。如果你在FactoryProcessor中使用guava,并且把注解和处理器打包在一个包中,这样的话,Android APK安装包中不只是包含FactoryProcessor的代码,而也包含了整个guava的代码。Guava有大约20000个方法。所以分开注解和处理器是非常有意义的。

    生成的类的实例化

    你已经看到了,在这个PizzaStore的例子中,生成了MealFactory类,它和其他手写的Java类没有任何区别。进而,你需要就想其他Java对象,手动实例化 它:

    [java] view plain copy
    1. public class PizzaStore {  
    2.   
    3.   private MealFactory factory = new MealFactory();  
    4.   
    5.   public Meal order(String mealName) {  
    6.     return factory.create(mealName);  
    7.   }  
    8.   ...  
    9. }  
    如果你是一个Android的开发者,你应该也非常熟悉一个叫做ButterKnife的注解处理器。在ButterKnife中,你使用@InjectView注解Android的View。ButterKnifeProcessor生成一个MyActivity$ $ViewInjector(),但是在ButterKnife你不需要手动调用new MyActivity$ $ViewInjector()实例化一个ButterKnife注入的对象,而是使用Butterknife.inject(activity)。ButterKnife内部使用反射机制来实例化MyActivity$$ViewInjector()对象:

    [java] view plain copy
    1. try {    
    2.     Class<?> injector = Class.forName(clsName + "$$ViewInjector");  
    3. catch (ClassNotFoundException e) { ... }  
    但是反射机制不是很慢么,我们使用注解处理来生成本地代码,会不会导致很多的反射性能的问题?的确,反射机制的性能确实是一个问题。然而,它不需要手动去创建对象,确实提高了开发者的开发速度。ButterKnife中有一个哈希表HashMap来缓存实例化过的对象。所以MyActivity$ $ViewInjector()只是使用反射机制实例化一次,第二次需要MyActivity$ $ViewInjector()的时候,就直接冲哈希表中获得。

    FragmentArgs非常类似于ButterKnife。它使用反射机制来创建对象,而不需要开发者手动来做这些。FragmentArgs在处理注解的时候生成一个特别的查找表类,它其实就是一种哈希表,所以整个FragmentArgs库只是在第一次使用的时候,执行一次反射调用,一旦整个Class.forName()的Fragemnt的参数对象被创建,后面的都是本地代码运行了。

    作为一个注解注解处理器的开发者,这些都由你来决定,为其他的注解器使用者,在反射和可用性上找到一个好的平衡。

    总结

    到此,我希望你对注解处理过程有一个非常深刻的理解。我必须再次说明一下:注解处理器是一个非常强大的工具,减少了很多无聊的代码的编写。我也想提醒的是,注解处理器可以做到比我上面提到的工厂模式的例子复杂很多的事情。例如,泛型的类型擦除,因为注解处理器是发生在类型擦除(type erasure)之前的(译者注:类型擦除可以参考这里)。就像你所看到的,你在写注解处理的时候,有两个普遍的问题你需要处理:第一问题, 如果你想在其他类中使用ElementUtils, TypeUtils和Messager,你就必须把他们作为参数传进去。在我为Android开发的注解器AnnotatedAdapter中,我尝试使用Dagger(一个依赖注入库)来解决这个问题。在这个简单的处理中使用它听起来有点过头了,但是它确实很好用;第二个问题,你必须做查询Elements的操作。就想我之前提到的,处理Element就解析XML或者HTML一样。对于HTML你可以是用jQuery,如果在注解处理器中,有类似于jQuery的库那那绝对是酷毙了。如果你知道有类似的库,请在下面的评论告诉我。

    请注意的是,在FactoryProcessor代码中有一些缺陷和陷阱。这些“错误”是我故意放进去的,是为了演示一些在开发过程中的常见错误(例如“Attempt to recreate a file”)。如果你想基于FactoryProcessor写你自己注解处理器,请不要直接拷贝粘贴这些陷阱过去,你应该从最开始就避免它们。

    展开全文
  • 如果没有用来读取注解的方法和...使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器注解处理器类库(java.lang.reflect.Ann...

    转载自https://www.cnblogs.com/peida/archive/2013/04/26/3038503.html

    如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。


    注解处理器类库(java.lang.reflect.AnnotatedElement):

      Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

      Class:类定义
      Constructor:构造器定义
      Field:累的成员变量定义
      Method:类的方法定义
      Package:类的包定义

      java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
      AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

      方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
      方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
      方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
      方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

      一个简单的注解处理器:  

    /***********注解声明***************/
    
    /**
     * 水果名称注解
     * @author peida
     *
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitName {
        String value() default "";
    }
    
    /**
     * 水果颜色注解
     * @author peida
     *
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitColor {
        /**
         * 颜色枚举
         * @author peida
         *
         */
        public enum Color{ BULE,RED,GREEN};
        
        /**
         * 颜色属性
         * @return
         */
        Color fruitColor() default Color.GREEN;
    
    }
    
    /**
     * 水果供应者注解
     * @author peida
     *
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitProvider {
        /**
         * 供应商编号
         * @return
         */
        public int id() default -1;
        
        /**
         * 供应商名称
         * @return
         */
        public String name() default "";
        
        /**
         * 供应商地址
         * @return
         */
        public String address() default "";
    }
    
    /***********注解使用***************/
    
    public class Apple {
        
        @FruitName("Apple")
        private String appleName;
        
        @FruitColor(fruitColor=Color.RED)
        private String appleColor;
        
        @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
        private String appleProvider;
        
        public void setAppleColor(String appleColor) {
            this.appleColor = appleColor;
        }
        public String getAppleColor() {
            return appleColor;
        }
        
        public void setAppleName(String appleName) {
            this.appleName = appleName;
        }
        public String getAppleName() {
            return appleName;
        }
        
        public void setAppleProvider(String appleProvider) {
            this.appleProvider = appleProvider;
        }
        public String getAppleProvider() {
            return appleProvider;
        }
        
        public void displayName(){
            System.out.println("水果的名字是:苹果");
        }
    }
    
    /***********注解处理器***************/
    
    public class FruitInfoUtil {
        public static void getFruitInfo(Class<?> clazz){
            
            String strFruitName=" 水果名称:";
            String strFruitColor=" 水果颜色:";
            String strFruitProvicer="供应商信息:";
            
            Field[] fields = clazz.getDeclaredFields();
            
            for(Field field :fields){
                if(field.isAnnotationPresent(FruitName.class)){
                    FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                    strFruitName=strFruitName+fruitName.value();
                    System.out.println(strFruitName);
                }
                else if(field.isAnnotationPresent(FruitColor.class)){
                    FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
                    strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
                    System.out.println(strFruitColor);
                }
                else if(field.isAnnotationPresent(FruitProvider.class)){
                    FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
                    strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
                    System.out.println(strFruitProvicer);
                }
            }
        }
    }
    
    /***********输出结果***************/
    public class FruitRun {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            
            FruitInfoUtil.getFruitInfo(Apple.class);
            
        }
    
    }
    
    ====================================
     水果名称:Apple
     水果颜色:RED
     供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦

    Java注解的基础知识点(见下面导图)基本都过了一遍,下一篇我们通过设计一个基于注解的简单的ORM框架,来综合应用和进一步加深对注解的各个知识点的理解和运用。

    展开全文
  • 注解处理器是干嘛的

    2019-07-14 19:01:39
    注解处理器初探     平时做项目中有个非常好用的一个插件,叫lombok.它提供了一些简单的注解,可以用来生成javabean和一些getter/setter方法,提高了开发的效率节省了开发时间. 今天我们就来看看...

    注解处理器初探

        平时做项目中有个非常好用的一个插件,叫lombok.它提供了一些简单的注解,可以用来生成javabean和一些getter/setter方法,提高了开发的效率节省了开发时间.
    今天我们就来看看lombok使用的什么方式来实现这种操作的.其实lombok使用的是annotation processor,这个是jdk1.5中增加的新功能.像@Getter只是一个注解,它真正的处理部分
    是在注解处理器里面实现的.官方参考链接.

    背景介绍

        注解处理器其实全称叫Pluggable Annotation Processing API,插入式注解处理器,它是对JSR269提案的实现,具体可以看链接里面的内容,JSR269链接.
    它是怎么工作的呢?可以参考下图:

    1.parse and enter:解析和输入,java编译器这个阶段会把源代码解析生成AST(抽象语法分析树)
    2.annotation processing:注解处理器阶段,此时将调用注解处理器,这时候可以校验代码,生成新文件等等(处理完可以循环到第一步)
    3.analyse and generate:分析和生成,此时前两步完成后,生成字节码(这个阶段进行了解糖,比如类型擦除)
    这些其实只是为了给大家留有一个粗浅的印象,它是怎么执行的.

    实践

        看了上面的资料,大脑中应该有了一个大概的印象,现在我们实际操作一下写一个简单的例子,实践一下.
    要使用注解处理器需要两个步骤:
    1.自定义一个注解
    2.继承AbstractProcessor并且实现process方法

    我们接下来写一个很简单的例子,就是在一个类上加上@InterfaceAnnotation,编译的时候去生成一个"I"+类名的接口类.
    首先我这里是定义了两个moudle,一个用来写注解和处理器,另一个用来调用注解.

    第一步:自定义一个注解

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface InterfaceAnnotation {
    }
    

    1.@Target:表示的是这个注解在什么上面使用,这里ElementType.TYPE是指在类上使用该注解
    2.@Retention:表示的是保留到什么阶段,这里RetentionPolicy.SOURCE是源代码阶段,编译后的class上就没有这个注解了

    第二步:继承AbstractProcessor并且实现process方法

    @SupportedAnnotationTypes(value = {"com.example.processor.InterfaceAnnotation"})
    @SupportedSourceVersion(value = SourceVersion.RELEASE_8)
    public class InterfaceProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            Messager messager = processingEnv.getMessager();
            messager.printMessage(Diagnostic.Kind.NOTE, "进入到InterfaceProcessor中了~~~");
            // 将带有InterfaceProcessor的类给找出来
            Set<? extends Element> clazz = roundEnv.getElementsAnnotatedWith(InterfaceAnnotation.class);
            clazz.forEach(item -> {
                // 生成一个 I + 类名的接口类
                String className = item.getSimpleName().toString();
                className = "I" + className.substring(0, 1) + className.substring(1);
                TypeSpec typeSpec = TypeSpec.interfaceBuilder(className).addModifiers(Modifier.PUBLIC).build();
    
                try {
                    // 生成java文件
                    JavaFile.builder("com.example.processor", typeSpec).build().writeTo(new File("./src/main/java/"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            return true;
        }
    }
    

    1.@SupportedAnnotationTypes:表示这个processor类要对什么注解生效
    2.@SupportedSourceVersion:表示支持的java版本
    3.annotations:被要求的注解,就是@SupportedAnnotationTypes对应的注解
    4.roundEnv:存放着当前和上一轮processing的环境信息
    5.TypeSpec这个可能有点没看懂是干嘛的,它是javaPoet中的一个类,javaPoet是java用于生成java文件的一款第三方插件很好用,所以这里使用了这个类来生成java文件,
    实际上这里用java自带的PrintWriter等输入输出流也可以生成java文件,生成文件有很多方式.javaPoet的链接.javaPoet使用指南.
    6.Messager是用来打印输出信息的,System.out.println其实也可以;
    7.process如果返回是true后续的注解处理器就不会再处理这个注解,如果是false,在下一轮processing中,其他注解处理器也会来处理改注解.

    写好之后,这里需要指定processor,META-INF/services/javax.annotation.processing.Processor 写好com.example.processor.InterfaceProcessor.如果你不知道这是啥,可以看下我另一篇博客(实力推广XD)什么是SPI
    我们在把注解处理器给编译好,maven里插件的设置:

    <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
                  <!-- 不加这一句编译会报找不到processor的异常-->
                  <compilerArgument>-proc:none</compilerArgument>
            </configuration>
    </plugin>
    

    此时的目录结构是这样:

    .
    ├── HELP.md
    ├── pom.xml
    ├── processor.iml
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── processor
            │               ├── InterfaceAnnotation.java
            │               └── InterfaceProcessor.java
            └── resources
                └── META-INF
                    └── services
                        └── javax.annotation.processing.Processor
    

    然后mvn clean install.

    第三步:使用注解

    在使用之前呢,注解处理器要是编译好的.引入注解处理器的jar包.
    测试类加上@InterfaceAnnotation

    @InterfaceAnnotation
    public class TestProcessor {
    }
    

    maven指定编译时使用的注解处理器.

    <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
                  <encoding>UTF-8</encoding>
                  <annotationProcessors>
                      <annotationProcessor>
                            com.example.processor.InterfaceProcessor
                      </annotationProcessor>
                  </annotationProcessors>
            </configuration>
    </plugin>
    

    此时目录结构是

    .
    ├── HELP.md
    ├── pom.xml
    ├── src
    │   └── main
    │       ├── java
    │       │   └── com
    │       │       └── example
    │       │           └── test
    │       │               └── TestProcessor.java
    │       └── resources
    └── test.iml
    

    然后mvn compile,生成了java文件,此时目录结构是:

    .
    ├── HELP.md
    ├── pom.xml
    ├── src
    │   └── main
    │       ├── java
    │       │   └── com
    │       │       └── example
    │       │           ├── processor
    │       │           │   └── ITestProcessor.java  // 这里就是生成的java文件
    │       │           └── test
    │       │               └── TestProcessor.java
    │       └── resources
    ├── target
    │   ├── classes
    │   │   └── com
    │   │       └── example
    │   │           └── test
    │   │               └── TestProcessor.class
    │   ├── generated-sources
    │   │   └── annotations
    │   └── maven-status
    │       └── maven-compiler-plugin
    │           └── compile
    │               └── default-compile
    │                   ├── createdFiles.lst
    │                   └── inputFiles.lst
    └── test.iml
    

    看到了生成的java文件就大功告成~

    总结:

    1.java注解处理器在很多地方都可以使用,实际应用比如lombok,安卓生成fragment等等,只使用一个注解可以省去很多代码,提高效率;
    2.本文只是列举了一个很简单的例子,很多注解处理器里面的api都没有使用到,读者有兴趣的可以自行研究,而且有涉及到抽象语法树的api;
    3.注解处理器可以用于生成新的类来完成某些功能,但是不能直接修改当前的类.

    参考资料:

    1.https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html
    2.https://jcp.org/aboutJava/communityprocess/final/jsr269/index.html
    3.https://github.com/square/javapoet
    4.https://www.cnblogs.com/throwable/p/9139908.html
    5.http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html(介绍处理过程)
    6.https://www.baeldung.com/java-annotation-processing-builder
    7.http://hannesdorfmann.com/annotation-processing/annotationprocessing101

    展开全文
  • Java注解处理器(编译时注解)

    千次阅读 2017-07-19 18:03:41
    很有幸阅读到一篇详细解释编写注解处理器的文章。本文的原文是ANNOTATION PROCESSING 101,作者是Hannes Dorfmann。这是一篇好文,忍不住翻译学习一下。以下是翻译。(注:本文的翻译已经获得了作者 Hannes 的授权。...

    https://race604.com/annotation-processing/

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的。一直想详细了解一下其中的原理。很有幸阅读到一篇详细解释编写注解处理器的文章。本文的原文是ANNOTATION PROCESSING 101,作者是Hannes Dorfmann。这是一篇好文,忍不住翻译学习一下。以下是翻译。(注:本文的翻译已经获得了作者 Hannes 的授权。)


    在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor)。在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么;然后,我将一步一步实现一个简单的注解器。

    一些基本概念

    在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。

    注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解。如果你不熟悉注解,你可以在这官方文档中得到更多信息。注解处理器在Java 5开始就有了,但是从Java 6(2006年12月发布)开始才有可用的API。过了一些时间,Java世界才意识到注解处理器的强大作用,所以它到最近几年才流行起来。

    一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这具体的含义什么呢?你可以生成Java代码!这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

    虚处理器AbstractProcessor

    我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:

    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): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, TypesFiler。后面我们将看到详细的内容。
    • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
    • getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
    • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者。

    在Java 7中,你也可以使用注解来代替getSupportedAnnotationTypes()getSupportedSourceVersion(),像这样:

    @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) { }
    }
    

    因为兼容的原因,特别是针对Android平台,我建议使用重载getSupportedAnnotationTypes()getSupportedSourceVersion()方法代替@SupportedAnnotationTypes@SupportedSourceVersion

    接下来的你必须知道的事情是,注解处理器是运行它自己的虚拟机JVM中。是的,你没有看错,javac启动一个完整Java虚拟机来运行注解处理器。这对你意味着什么?你可以使用任何你在其他java应用中使用的的东西。使用guava。如果你愿意,你可以使用依赖注入工具,例如dagger或者其他你想要的类库。但是不要忘记,即使是一个很小的处理,你也要像其他Java应用一样,注意算法效率,以及设计模式。

    注册你的处理器

    你可能会问,我怎样将处理器MyProcessor注册到javac中。你必须提供一个.jar文件。就像其他.jar文件一样,你打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件javax.annotation.processing.ProcessorMETA-INF/services路径下。所以,你的.jar文件看起来就像下面这样:

    • MyProcessor.jar
      • com
        • example
          • MyProcessor.class
      • META-INF
        • services
          • javax.annotation.processing.Processor

    打包进MyProcessor.jar中的javax.annotation.processing.Processor的内容是,注解处理器的合法的全名列表,每一个元素换行分割:

    com.example.MyProcessor  
    com.foo.OtherProcessor  
    net.blabla.SpecialProcessor  
    

    MyProcessor.jar放到你的builpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器。

    例子:工厂模式

    是时候来说一个实际的例子了。我们将使用maven工具来作为我们的编译系统和依赖管理工具。如果你不熟悉maven,不用担心,因为maven不是必须的。本例子的完成代码在Github上。

    开始之前,我必须说,要为这个教程找到一个需要用注解处理器解决的简单问题,实在并不容易。这里我们将实现一个非常简单的工厂模式(不是抽象工厂模式)。这将对注解处理器的API做一个非常简明的介绍。所以,这个问题的程序并不是那么有用,也不是一个真实世界的例子。所以在此申明,你将学习关于注解处理过程的相关内容,而不是设计模式。

    我们将要解决的问题是:我们将实现一个披萨店,这个披萨店给消费者提供两种披萨(“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;
      }
    }
    

    为了在我们的披萨店PizzsStore下订单,消费者需要输入餐(Meal)的名字。

    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());
      }
    }
    

    正如你所见,在order()方法中,我们有很多的if语句,并且如果我们每添加一种新的披萨,我们都要添加一条新的if语句。但是等一下,使用注解处理和工厂模式,我们可以让注解处理器来帮我们自动生成这些if语句。如此以来,我们期望的是如下的代码:

    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应该是如下的样子:

    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注解

    你能猜到么:我们想用注解处理器自动生成MealFactory。更一般的说,我们将想要提供一个注解和一个处理器来生成工厂类。

    我们先来看一下@Factory注解:

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS)
    public @interface Factory {
    
      /**
       * 工厂的名字
       */
      Class type();
    
      /**
       * 用来表示生成哪个对象的唯一id
       */
      String id();
    }
    

    想法是这样的:我们将使用同样的type()注解那些属于同一个工厂的类,并且用注解的id()做一个映射,例如从"Calzone"映射到"ClzonePizza"类。我们应用@Factory注解到我们的类中,如下:

    @Factory(
        id = "Margherita",
        type = Meal.class
    )
    public class MargheritaPizza implements Meal {
    
      @Override public float getPrice() {
        return 6f;
      }
    }
    
    @Factory(
        id = "Calzone",
        type = Meal.class
    )
    public class CalzonePizza implements Meal {
    
      @Override public float getPrice() {
        return 8.5f;
      }
    }
    
    @Factory(
        id = "Tiramisu",
        type = Meal.class
    )
    public class Tiramisu implements Meal {
    
      @Override public float getPrice() {
        return 4.5f;
      }
    }
    

    你可能会问你自己,我们是否可以只把@Factory注解应用到我们的Meal接口上?答案是,注解是不能继承的。一个类class X被注解,并不意味着它的子类class Y extends X会自动被注解。在我们开始写处理器的代码之前,我们先规定如下一些规则:

    1. 只有类可以被@Factory注解,因为接口或者抽象类并不能用new操作实例化;
    2. @Factory注解的类,必须至少提供一个公开的默认构造器(即没有参数的构造函数)。否者我们没法实例化一个对象。
    3. @Factory注解的类必须直接或者间接的继承于type()指定的类型;
    4. 具有相同的type的注解类,将被聚合在一起生成一个工厂类。这个生成的类使用Factory后缀,例如type = Meal.class,将生成MealFactory工厂类;
    5. id只能是String类型,并且在同一个type组中必须唯一。

    处理器

    我将通过添加代码和一段解释的方法,一步一步的引导你来构建我们的处理器。省略号(...)表示省略那些已经讨论过的或者将在后面的步骤中讨论的代码,目的是为了让我们的代码有更好的可读性。正如我们前面说的,我们完整的代码可以在Github上找到。好了,让我们来看一下我们的处理器类FactoryProcessor的骨架:

    @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),这是什么?这是一个其他注解处理器中引入的注解。AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。是的,你没有看错,我们可以在注解处理器中使用注解。非常方便,难道不是么?在getSupportedAnnotationTypes()中,我们指定本处理器将处理@Factory注解。

    Elements和TypeMirrors

    init()中我们获得如下引用:

    • Elements:一个用来处理Element的工具类(后面将做详细说明);
    • Types:一个用来处理TypeMirror的工具类(后面将做详细说明);
    • Filer:正如这个名字所示,使用Filer你可以创建文件。

    在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个:

    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   // VariableElement
                         ) {}
    }
    

    你必须换个角度来看源代码,它只是结构化的文本,他不是可运行的。你可以想象它就像你将要去解析的XML文件一样(或者是编译器中抽象的语法树)。就像XML解释器一样,有一些类似DOM的元素。你可以从一个元素导航到它的父或者子元素上。

    举例来说,假如你有一个代表public class Foo类的TypeElement元素,你可以遍历它的孩子,如下:

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

    正如你所见,Element代表的是源代码。TypeElement代表的是源代码中的类型元素,例如类。然而,TypeElement并不包含类本身的信息。你可以从TypeElement中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过TypeMirror获取。你可以通过调用elements.asType()获取元素的TypeMirror

    搜索@Factory注解

    我们来一步一步实现process()方法。首先,我们从搜索被注解了@Factory的类开始:

    @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)) {
              ...
        }
      }
     ...
    }
    

    这里并没有什么高深的技术。roundEnv.getElementsAnnotatedWith(Factory.class))返回所有被注解了@Factory的元素的列表。你可能已经注意到,我们并没有说“所有被注解了@Factory的类的列表”,因为它真的是返回Element的列表。请记住:Element可以是类、方法、变量等。所以,接下来,我们必须检查这些Element是否是一个类:

    @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
    
          // 检查被注解为@Factory的元素是否是一个类
          if (annotatedElement.getKind() != ElementKind.CLASS) {
                ...
          }
       }
       ...
    }
    

    为什么要这么做?我们要确保只有class元素被我们的处理器处理。前面我们已经学习到类是用TypeElement表示。我们为什么不这样判断呢if (! (annotatedElement instanceof TypeElement) )?这是错误的,因为接口(interface)类型也是TypeElement。所以在注解处理器中,我们要避免使用instanceof,而是配合TypeMirror使用EmentKind或者TypeKind

    错误处理

    init()中,我们也获得了一个Messager对象的引用。Messager提供给注解处理器一个报告错误、警告以及提示信息的途径。它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。在官方文档中描述了消息的不同级别。非常重要的是Kind.ERROR,因为这种类型的信息用来表示我们的注解处理器处理失败了。很有可能是第三方开发者错误的使用了@Factory注解(例如,给接口使用了@Factory注解)。这个概念和传统的Java应用有点不一样,在传统Java应用中我们可能就抛出一个异常Exception。如果你在process()中抛出一个异常,那么运行注解处理器的JVM将会崩溃(就像其他Java应用一样),使用我们注解处理器FactoryProcessor第三方开发者将会从javac中得到非常难懂的出错信息,因为它包含FactoryProcessor的堆栈跟踪(Stacktace)信息。因此,注解处理器就有一个Messager类,它能够打印非常优美的错误信息。除此之外,你还可以连接到出错的元素。在像IntelliJ这种现代的IDE(集成开发环境)中,第三方开发者可以直接点击错误信息,IDE将会直接跳转到第三方开发者项目的出错的源文件的相应的行。

    我们重新回到process()方法的实现。如果遇到一个非类类型被注解@Factory,我们发出一个出错信息:

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
    
          // 检查被注解为@Factory的元素是否是一个类
          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显示相关出错信息,更重要的是注解处理器程序必须完成运行而不崩溃,这就是为什么在调用error()后直接return。如果我们不直接返回,process()将继续运行,因为messager.printMessage( Diagnostic.Kind.ERROR)不会停止此进程。因此,如果我们在打印错误信息以后不返回的话,我们很可能就会运行到一个NullPointerException等。就像我们前面说的,如果我们继续运行process(),问题是如果在process()中抛出一个未处理的异常,javac将会打印出内部的NullPointerException,而不是Messager中的错误信息。

    数据模型

    在继续检查被注解@Fractory的类是否满足我们上面说的5条规则之前,我们将介绍一个让我们更方便继续处理的数据结构。有时候,一个问题或者解释器看起来如此简单,以至于程序员倾向于用一个面向过程方式来写整个处理器。但是你知道吗?一个注解处理器任然是一个Java程序,所以我们需要使用面向对象编程、接口、设计模式,以及任何你将在其他普通Java程序中使用的技巧。

    我们的FactoryProcessor非常简单,但是我们仍然想要把一些信息存为对象。在FactoryAnnotatedClass中,我们保存被注解类的数据,比如合法的类的名字,以及@Factory注解本身的一些信息。所以,我们保存TypeElement和处理过的@Factory注解:

    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()} 中指定的类型的简单名字
       *
       * @return qualified name
       */
      public String getSimpleFactoryGroupName() {
        return simpleTypeName;
      }
    
      /**
       * 获取被@Factory注解的原始元素
       */
      public TypeElement getTypeElement() {
        return annotatedClassElement;
      }
    }
    

    代码很多,但是最重要的部分是在构造函数中。其中你能找到如下的代码:

    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异常。你可能感到疑惑的是,前面我们说了不要抛出异常,而是使用Messager。这里仍然不矛盾。我们抛出内部的异常,你在将在后面看到会在process()中捕获这个异常。我这样做出于一下两个原因:

    1. 我想示意我们应该像普通的Java程序一样编码。抛出和捕获异常是非常好的Java编程实践;
    2. 如果我们想要在FactoryAnnotatedClass中打印信息,我需要也传入Messager对象,并且我们在错误处理一节中已经提到,为了打印Messager信息,我们必须成功停止处理器运行。如果我们使用Messager打印了错误信息,我们怎样告知process()出现了错误呢?最容易,并且我认为最直观的方式就是抛出一个异常,然后让process()捕获之。

    接下来,我们将获取@Fractory注解中的type成员。我们比较关心的是合法的全名:

    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();
    }
    

    这里有一点小麻烦,因为这里的类型是一个java.lang.Class。这就意味着,他是一个真正的Class对象。因为注解处理是在编译Java源代码之前。我们需要考虑如下两种情况:

    1. 这个类已经被编译:这种情况是:如果第三方.jar包含已编译的被@Factory注解.class文件。在这种情况下,我们可以想try中那块代码中所示直接获取Class
    2. 这个还没有被编译:这种情况是我们尝试编译被@Fractory注解的源代码。这种情况下,直接获取Class会抛出MirroredTypeException异常。幸运的是,MirroredTypeException包含一个TypeMirror,它表示我们未编译类。因为我们已经知道它必定是一个类类型(我们已经在前面检查过),我们可以直接强制转换为DeclaredType,然后读取TypeElement来获取合法的名字。

    好了,我们现在还需要一个数据结构FactoryGroupedClasses,它将简单的组合所有的FactoryAnnotatedClasses到一起。

    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 {
        ...
      }
    }
    

    正如你所见,这是一个基本的Map<String, FactoryAnnotatedClass>,这个映射表用来映射@Factory.id()到FactoryAnnotatedClass。我们选择Map这个数据类型,是因为我们要确保每个id是唯一的,我们可以很容易通过map查找实现。generateCode()方法将被用来生成工厂类代码(将在后面讨论)。

    匹配标准

    我们继续实现process()方法。接下来我们想要检查被注解的类必须有只要一个公开的构造函数,不是抽象类,继承于特定的类型,以及是一个公开类:

    public class FactoryProcessor extends AbstractProcessor {
    
      @Override
      public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
    
          ...
    
          // 因为我们已经知道它是ElementKind.CLASS类型,所以可以直接强制转换
          TypeElement typeElement = (TypeElement) annotatedElement;
    
          try {
            FactoryAnnotatedClass annotatedClass =
                new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException
    
            if (!isValidClass(annotatedClass)) {
              return true; // 已经打印了错误信息,退出处理过程
             }
           } catch (IllegalArgumentException e) {
            // @Factory.id()为空
            error(typeElement, e.getMessage());
            return true;
           }
              ...
       }
    
     private boolean isValidClass(FactoryAnnotatedClass item) {
    
        // 转换为TypeElement, 含有更多特定的方法
        TypeElement classElement = item.getTypeElement();
    
        if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
          error(classElement, "The class %s is not public.",
              classElement.getQualifiedName().toString());
          return false;
        }
    
        // 检查是否是一个抽象类
        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;
        }
    
        // 检查继承关系: 必须是@Factory.type()指定的类型子类
        TypeElement superClassElement =
            elementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
        if (superClassElement.getKind() == ElementKind.INTERFACE) {
          // 检查接口是否实现了                                       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) {
              // 到达了基本类型(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;
            }
    
            // 在继承树上继续向上搜寻
            currentClass = (TypeElement) typeUtils.asElement(superClassType);
          }
        }
    
        // 检查是否提供了默认公开构造函数
        for (Element enclosed : classElement.getEnclosedElements()) {
          if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
            ExecutableElement constructorElement = (ExecutableElement) enclosed;
            if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers()
                .contains(Modifier.PUBLIC)) {
              // 找到了默认构造函数
              return true;
            }
          }
        }
    
        // 没有找到默认构造函数
        error(classElement, "The class %s must provide an public empty default constructor",
            classElement.getQualifiedName().toString());
        return false;
      }
    }
    

    我们这里添加了isValidClass()方法,来检查是否我们所有的规则都被满足了:

    • 必须是公开类:classElement.getModifiers().contains(Modifier.PUBLIC)
    • 必须是非抽象类:classElement.getModifiers().contains(Modifier.ABSTRACT)
    • 必须是@Factoy.type()指定的类型的子类或者接口的实现:首先我们使用elementUtils.getTypeElement(item.getQualifiedFactoryGroupName())创建一个传入的Class(@Factoy.type())的元素。是的,你可以仅仅通过已知的合法类名来直接创建TypeElement(使用TypeMirror)。接下来我们检查它是一个接口还是一个类:superClassElement.getKind() == ElementKind.INTERFACE。所以我们这里有两种情况:如果是接口,就判断classElement.getInterfaces().contains(superClassElement.asType());如果是类,我们就必须使用currentClass.getSuperclass()扫描继承层级。注意,整个检查也可以使用typeUtils.isSubtype()来实现。
    • 类必须有一个公开的默认构造函数:我们遍历所有的闭元素classElement.getEnclosedElements(),然后检查ElementKind.CONSTRUCTORModifier.PUBLIC以及constructorElement.getParameters().size() == 0

    如果所有这些条件都满足,isValidClass()返回true,否者就打印错误信息,并且返回false

    组合被注解的类

    一旦我们检查isValidClass()成功,我们将添加FactoryAnnotatedClass到对应的FactoryGroupedClasses中,如下:

    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; // 错误信息被打印,退出处理流程
            }
    
            // 所有检查都没有问题,所以可以添加了
            FactoryGroupedClasses factoryClass =
            factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
            if (factoryClass == null) {
              String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
              factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
              factoryClasses.put(qualifiedGroupName, factoryClass);
            }
    
            // 如果和其他的@Factory标注的类的id相同冲突,
            // 抛出IdAlreadyUsedException异常
            factoryClass.add(annotatedClass);
          } catch (IllegalArgumentException e) {
            // @Factory.id()为空 --> 打印错误信息
            error(typeElement, e.getMessage());
            return true;
          } catch (IdAlreadyUsedException e) {
            FactoryAnnotatedClass existing = e.getExisting();
            // 已经存在
            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;
          }
        }
        ...
    }
    

    代码生成

    我们已经收集了所有的被@Factory注解的类保存到为FactoryAnnotatedClass,并且组合到了FactoryGroupedClasses。现在我们将为每个工厂生成Java文件了:

    @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文件,和写其他普通文件没有什么两样。使用Filer提供的Writer对象,我们可以连接字符串来写我们生成的Java代码。幸运的是,Square公司(因为提供了许多非常优秀的开源项目二非常有名)给我们提供了JavaWriter,这是一个高级的生成Java代码的库:

    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();
      }
    }
    

    注意:因为JavaWriter非常非常的流行,所以很多处理器、库、工具都依赖于JavaWriter。如果你使用依赖管理工具,例如maven或者gradle,假如一个库依赖的JavaWriter的版本比其他的库新,这将会导致一些问题。所以我建议你直接拷贝重新打包JavaWiter到你的注解处理器代码中(实际它只是一个Java文件)。

    更新:JavaWrite现在已经被JavaPoet取代了。

    处理循环

    注解处理过程可能会多于一次。官方javadoc定义处理过程如下:

    注解处理过程是一个有序的循环过程。在每次循环中,一个处理器可能被要求去处理那些在上一次循环中产生的源文件和类文件中的注解。第一次循环的输入是运行此工具的初始输入。这些初始输入,可以看成是虚拟的第0此的循环的输出。

    一个简单的定义:一个处理循环是调用一个注解处理器的process()方法。对应到我们的工厂模式的例子中:FactoryProcessor被初始化一次(不是每次循环都会新建处理器对象),然而,如果生成了新的源文件process()能够被调用多次。听起来有点奇怪不是么?原因是这样的,这些生成的文件中也可能包含@Factory注解,它们还将会被FactoryProcessor处理。

    例如我们的PizzaStore的例子中将会经过3次循环处理:

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

    我解释处理循环还有另外一个原因。如果你看一下我们的FactoryProcessor代码你就能注意到,我们收集数据和保存它们在一个私有的域中Map<String, FactoryGroupedClasses> factoryClasses。在第一轮中,我们检测到了MagheritaPizza, CalzonePizza和Tiramisu,然后生成了MealFactory.java。在第二轮中把MealFactory作为输入。因为在MealFactory中没有检测到@Factory注解,我们预期并没有错误,然而我们得到如下的信息:

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

    这个问题是因为我们没有清除factoryClasses,这意味着,在第二轮的process()中,任然保存着第一轮的数据,并且会尝试生成在第一轮中已经生成的文件,从而导致这个错误的出现。在我们的这个场景中,我们知道只有在第一轮中检查@Factory注解的类,所以我们可以简单的修复这个问题,如下:

    @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;
    }
    

    我知道这有其他的方法来处理这个问题,例如我们也可以设置一个布尔值标签等。关键的点是:我们要记住注解处理过程是需要经过多轮处理的,并且你不能重载或者重新创建已经生成的源代码。

    分离处理器和注解

    如果你已经看了我们的代码库,你将发现我们组织我们的代码到两个maven模块中了。我们这么做是因为,我们想让我们的工厂模式的例子的使用者,在他们的工程中只编译注解,而包含处理器模块只是为了编译。有点晕?我们举个例子,如果我们只有一个包。如果另一个开发者想要把我们的工厂模式处理器用于他的项目中,他就必须包含@Factory注解和整个FactoryProcessor的代码(包括FactoryAnnotatedClass和FactoryGroupedClasses)到他们项目中。我非常确定的是,他并不需要在他已经编译好的项目中包含处理器相关的代码。如果你是一个Android的开发者,你肯定听说过65k个方法的限制(即在一个.dex文件中,只能寻址65000个方法)。如果你在FactoryProcessor中使用guava,并且把注解和处理器打包在一个包中,这样的话,Android APK安装包中不只是包含FactoryProcessor的代码,而也包含了整个guava的代码。Guava有大约20000个方法。所以分开注解和处理器是非常有意义的。

    生成的类的实例化

    你已经看到了,在这个PizzaStore的例子中,生成了MealFactory类,它和其他手写的Java类没有任何区别。进而,你需要就想其他Java对象,手动实例化它:

    public class PizzaStore {
    
      private MealFactory factory = new MealFactory();
    
      public Meal order(String mealName) {
        return factory.create(mealName);
      }
      ...
    }
    

    如果你是一个Android的开发者,你应该也非常熟悉一个叫做ButterKnife的注解处理器。在ButterKnife中,你使用@InjectView注解Android的View。ButterKnifeProcessor生成一个MyActivity$$ViewInjector,但是在ButterKnife你不需要手动调用new MyActivity$$ViewInjector()实例化一个ButterKnife注入的对象,而是使用Butterknife.inject(activity)。ButterKnife内部使用反射机制来实例化MyActivity$$ViewInjector()对象:

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

    但是反射机制不是很慢么,我们使用注解处理来生成本地代码,会不会导致很多的反射性能的问题?的确,反射机制的性能确实是一个问题。然而,它不需要手动去创建对象,确实提高了开发者的开发速度。ButterKnife中有一个哈希表HashMap来缓存实例化过的对象。所以MyActivity$$ViewInjector只是使用反射机制实例化一次,第二次需要MyActivity$$ViewInjector的时候,就直接冲哈希表中获得。

    FragmentArgs非常类似于ButterKnife。它使用反射机制来创建对象,而不需要开发者手动来做这些。FragmentArgs在处理注解的时候生成一个特别的查找表类,它其实就是一种哈希表,所以整个FragmentArgs库只是在第一次使用的时候,执行一次反射调用,一旦整个Class.forName()的Fragemnt的参数对象被创建,后面的都是本地代码运行了。

    作为一个注解注解处理器的开发者,这些都由你来决定,为其他的注解器使用者,在反射和可用性上找到一个好的平衡。

    总结

    到此,我希望你对注解处理过程有一个非常深刻的理解。我必须再次说明一下:注解处理器是一个非常强大的工具,减少了很多无聊的代码的编写。我也想提醒的是,注解处理器可以做到比我上面提到的工厂模式的例子复杂很多的事情。例如,泛型的类型擦除,因为注解处理器是发生在类型擦除(type erasure)之前的(译者注:类型擦除可以参考这里)。就像你所看到的,你在写注解处理的时候,有两个普遍的问题你需要处理:第一问题, 如果你想在其他类中使用ElementUtils, TypeUtils和Messager,你就必须把他们作为参数传进去。在我为Android开发的注解器AnnotatedAdapter中,我尝试使用Dagger(一个依赖注入库)来解决这个问题。在这个简单的处理中使用它听起来有点过头了,但是它确实很好用;第二个问题,你必须做查询Elements的操作。就想我之前提到的,处理Element就解析XML或者HTML一样。对于HTML你可以是用jQuery,如果在注解处理器中,有类似于jQuery的库那那绝对是酷毙了。如果你知道有类似的库,请在下面的评论告诉我。

    请注意的是,在FactoryProcessor代码中有一些缺陷和陷阱。这些“错误”是我故意放进去的,是为了演示一些在开发过程中的常见错误(例如“Attempt to recreate a file”)。如果你想基于FactoryProcessor写你自己注解处理器,请不要直接拷贝粘贴这些陷阱过去,你应该从最开始就避免它们。

    我在后续的博客中将会写注解处理器的单元测试,敬请关注。


    翻译到此结束。文章确实太长了,花了我好几天的时间,如果翻译过程中有什么错误,请指出来,也可以参考作者的原文。翻译不容易,写这篇文章的原作者更不容易。能够在这种优秀的博文中,学习到作者的认真的态度也是很值得的。

    展开全文
  • 自定义注解处理器生成代码 先上一段代码: public class MainActivity extends AppCompatActivity { @BindView(R.id.tv) TextView tv; @BindView(R.id.tv2) TextView tv2; @BindView(R.id.tv3...
  • 在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor)。在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么;然后,我将一步一步实现一个简单的注解器。 ...
  • 注解处理器(Annoation Processor)

    千次阅读 2019-04-02 18:11:36
    前言 Java中的注解(Annotation)如果要被识别,离不开注解处理器。...Java中有默认的注解处理器,使用者也可以自定义注解处理器,注册后使用注解处理器处理注解,最终达到注解本身起到的效果。 注解处理器将...
  • java注解之注解处理器

    千次阅读 2018-10-08 14:39:06
    摘要  Java5中提供了apt工具来进行编译期的注解处理。...通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑。...编写注解处理器的核心是两个类:注解处理器(com.sun.mirror.apt....
  • 注解和注解处理器

    千次阅读 2016-02-29 17:09:31
    注解机制 = 注解 + 注解处理器
  • 自定义Java注解处理器

    千次阅读 2018-09-10 10:13:01
    该文章是继Java注解解析-基础+运行时注解(RUNTIME)之后,使用注解处理器处理CLASS注解的文章。通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己的注解处理器。前提是你知道如何...
  • Java注解处理器使用注意事项 目录 [TOC] 注解 元注解 @Target 用于标记可以应用于哪些类型上. 类型说明 元素类型 适用场合 ANOTATION_TYPE 注解类型声明 PACKAGE 包 TYPE 类,枚举,接口,...
  • Java 注解与注解处理器

    千次阅读 2019-09-08 23:23:55
    Java 注解与注解处理器 从 JDK 5 开始,Java 增加了注解,注解是代码里面的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行一些相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,在...
  • Java 注解及自定义注解处理器

    千次阅读 2018-03-20 11:08:49
    注解介绍 ...Java注解是在Java SE5中被引入进来的,在Java中内置了三种注解以及四种元注解。 内置注解 内置注解 说明 @Override 表示当前的方法定义将覆盖超类中的方法,如果方法名或者...
  • 原文作者:deors  原文地址:... ... 版本信息:本文基于2015-10-16版本进行翻译  版权声明:本文经原文作者许可进行翻译,保留所有权利,未经允许不得复制和转载 ...
  • Java注解处理器

    2019-04-01 11:36:36
    Java注解处理器 09 February 2015 on Android Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的。一直想详细了解一下其中的原理。很有幸阅读到一篇详细解释编写...
  • 该文章讲解了如何处理源码注解(CLASS ),是继Java注解解析-基础+运行时注解(RUNTIME)之,使用注解处理器处理CLASS注解的文章。通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己...
  • 1:gradle.properties org.gradle.daemon=true org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 2:Edit Configuration... 添加remote 然后Apply 默认确定, ...
  • 对于注解,如果没有注解处理器,其作用和注释没有多大区别。简单的注解处理器(运行时)示例: package com.mango.annotationUtil; import java.lang.reflect.Field; import com.mango.annotation.FruitColor; ...
  • Java编译时注解处理器(APT)详解

    万次阅读 多人点赞 2019-08-24 03:18:44
    上篇文章我们使用注解+反射实现了一个仿ButterKnife功能的示例。考虑到反射是在运行时完成的,多少会影响程序性能。因此,ButterKnife本身并非基于注解+反射来实现的,而是用Annotation Processor在编译时处理注解的...
  • AbstractProcessor是注解处理器的抽象类,我们通过继承AbstractProcessor类然后实现process方法来创建我们自己的注解处理器,所有处理注解的代码放在process方法里面。一个简单的处理器示例如下: //Annotation...

空空如也

1 2 3 4 5 ... 20
收藏数 67,400
精华内容 26,960
关键字:

注解处理器