• Java注解处理器--编译时处理的注解


    1. 一些基本概念

    在开始之前,我们需要声明一件重要的事情是:我们不是在讨论在运行时通过反射机制运行处理的注解,而是在讨论在编译时处理的注解。
    注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。这里,我假设你已经了解什么是注解及如何自定义注解。如果你还未了解注解的话,可以查看官方文档。注解处理器在 Java 5 的时候就已经存在了,但直到 Java 6 (发布于2006看十二月)的时候才有可用的API。过了一段时间java的使用者们才意识到注解处理器的强大。所以最近几年它才开始流行。
    一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java文件)作为输出。那意味着什么呢?你可以生成 java 代码!这些 java 代码在生成的.java文件中。因此你不能改变已经存在的java类,例如添加一个方法。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译。

    Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。

    2. AbstractProcessor

    让我们来看一下处理器的 API。所有的处理器都继承了AbstractProcessor,如下所示:

     1 package com.example;
     2 
     3 import java.util.LinkedHashSet;
     4 import java.util.Set;
     5 import javax.annotation.processing.AbstractProcessor;
     6 import javax.annotation.processing.ProcessingEnvironment;
     7 import javax.annotation.processing.RoundEnvironment;
     8 import javax.annotation.processing.SupportedAnnotationTypes;
     9 import javax.annotation.processing.SupportedSourceVersion;
    10 import javax.lang.model.SourceVersion;
    11 import javax.lang.model.element.TypeElement;
    12 
    13 public class MyProcessor extends AbstractProcessor {
    14 
    15     @Override
    16     public boolean process(Set<? extends TypeElement> annoations,
    17             RoundEnvironment env) {
    18         return false;
    19     }
    20 
    21     @Override
    22     public Set<String> getSupportedAnnotationTypes() {
    23         Set<String> annotataions = new LinkedHashSet<String>();
    24         annotataions.add("com.example.MyAnnotation");
    25         return annotataions;
    26     }
    27 
    28     @Override
    29     public SourceVersion getSupportedSourceVersion() {
    30         return SourceVersion.latestSupported();
    31     }
    32 
    33     @Override
    34     public synchronized void init(ProcessingEnvironment processingEnv) {
    35         super.init(processingEnv);
    36     }
    37 
    38 }
    • init(ProcessingEnvironment processingEnv) :所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以ProcessingEnvironment作为参数。ProcessingEnvironment 提供了一些实用的工具类ElementsTypesFiler。我们在后面将会使用到它们。

    • process(Set<? extends TypeElement> annoations, RoundEnvironment env) :这类似于每个处理器的main()方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment 参数,你可以查询被特定注解标注的元素(原文:you can query for elements annotated with a certain annotation )。后面我们将会看到详细内容。

    • getSupportedAnnotationTypes():在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。

    • getSupportedSourceVersion() : 用来指定你使用的 java 版本。通常你应该返回SourceVersion.latestSupported() 。不过,如果你有足够的理由坚持用 java 6 的话,你也可以返回SourceVersion.RELEASE_6。我建议使用SourceVersion.latestSupported()。在 Java 7 中,你也可以使用注解的方式来替代重写getSupportedAnnotationTypes() 和 getSupportedSourceVersion(),如下所示:

    @SupportedSourceVersion(value=SourceVersion.RELEASE_7)
    @SupportedAnnotationTypes({
       // Set of full qullified annotation type names
        "com.example.MyAnnotation",
        "com.example.AnotherAnnotation"
     })
    public class MyProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annoations,
                RoundEnvironment env) {
            return false;
        }
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
        }
    }

    由于兼容性问题,特别是对于 android ,我建议重写getSupportedAnnotationTypes() 和 getSupportedSourceVersion() ,而不是使用 @SupportedAnnotationTypes 和 @SupportedSourceVersion

    接下来你必须知道的事情是:注解处理器运行在它自己的 JVM 中。是的,你没看错。javac 启动了一个完整的 java 虚拟机来运行注解处理器。这意味着什么?你可以使用任何你在普通 java 程序中使用的东西。使用 guava! 你可以使用依赖注入工具,比如dagger或者任何其他你想使用的类库。但不要忘记,即使只是一个小小的处理器,你也应该注意使用高效的算法及设计模式,就像你在开发其他 java 程序中所做的一样。

    3. 注册你的处理器

    你可能会问 “怎样注册我的注解处理器到 javac ?”。你必须提供一个.jar文件。就像其他 .jar 文件一样,你将你已经编译好的注解处理器打包到此文件中。并且,在你的 .jar 文件中,你必须打包一个特殊的文件javax.annotation.processing.ProcessorMETA-INF/services目录下。因此你的 .jar 文件目录结构看起来就你这样:

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

    javax.annotation.processing.Processor 文件的内容是一个列表,每一行是一个注解处理器的全称。例如:

    com.example.MyProcess
    com.example.AnotherProcess


    4. 例子:工厂模式

    我们要解决的问题是:我们要实现一个 pizza 店,这个 pizza 店提供给顾客两种 pizza (Margherita 和 Calzone),还有甜点 Tiramisu(提拉米苏)。

     1 public interface Meal {
     2     public float getPrice();
     3 }
     4 public class MargheritaPizza implements Meal{
     5     @Override
     6     public float getPrice() {
     7         return 6.0f;
     8     }
     9 }
    10 public class CalzonePizza implements Meal{
    11     @Override
    12     public float getPrice() {
    13         return 8.5f;
    14     }
    15 }
    16 public class Tiramisu implements Meal{
    17     @Override
    18     public float getPrice() {
    19         return 4.5f;
    20     }
    21 }
    22 
    23 public class PizzaStore {
    24 
    25     public Meal order(String mealName) {
    26         if (null == mealName) {
    27             throw new IllegalArgumentException("name of meal is null!");
    28         }
    29         if ("Margherita".equals(mealName)) {
    30             return new MargheritaPizza();
    31         }
    32 
    33         if ("Calzone".equals(mealName)) {
    34             return new CalzonePizza();
    35         }
    36 
    37         if ("Tiramisu".equals(mealName)) {
    38             return new Tiramisu();
    39         }
    40 
    41         throw new IllegalArgumentException("Unknown meal '" + mealName + "'");
    42     }
    43 
    44     private static String readConsole() {
    45         Scanner scanner = new Scanner(System.in);
    46         String meal = scanner.nextLine();
    47         scanner.close();
    48         return meal;
    49     }
    50     
    51     public static void main(String[] args) {
    52         System.out.println("welcome to pizza store");
    53         PizzaStore pizzaStore = new PizzaStore();
    54         Meal meal = pizzaStore.order(readConsole());
    55         System.out.println("Bill:$" + meal.getPrice());
    56     }
    57 }

    正如你所见,在order()方法中,我们有许多 if 条件判断语句。并且,如果我们添加一种新的 pizza 的话,我们就得添加一个新的 if 条件判断。但是等一下,使用注解处理器和工厂模式,我们可以让一个注解处理器生成这些 if 语句。如此一来,我们想要的代码就像这样子:

     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     private static String readConsole() {
    10         Scanner scanner = new Scanner(System.in);
    11         String meal = scanner.nextLine();
    12         scanner.close();
    13         return meal;
    14     }
    15     
    16     public static void main(String[] args) {
    17         System.out.println("welcome to pizza store");
    18         PizzaStore pizzaStore = new PizzaStore();
    19         Meal meal = pizzaStore.order(readConsole());
    20         System.out.println("Bill:$" + meal.getPrice());
    21     }
    22 }
    23 
    24 public class MealFactory {
    25 
    26     public Meal create(String id) {
    27         if (id == null) {
    28             throw new IllegalArgumentException("id is null!");
    29         }
    30         if ("Calzone".equals(id)) {
    31             return new CalzonePizza();
    32         }
    33 
    34         if ("Tiramisu".equals(id)) {
    35             return new Tiramisu();
    36         }
    37 
    38         if ("Margherita".equals(id)) {
    39             return new MargheritaPizza();
    40         }
    41 
    42         throw new IllegalArgumentException("Unknown id = " + id);
    43     }
    44 }

    5. @Factory Annotation

    能猜到么,我们打算使用注解处理器生成MealFactory类。更一般的说,我们想要提供一个注解和一个处理器用来生成工厂类。
    让我们看一下@Factory注解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface Factory {
    
        /**
         * The name of the factory
         */
        Class<?> type();
    
        /**
         * The identifier for determining which item should be instantiated
         */
        String id();
    }

    思想是这样的:我们注解那些食物类,使用type()表示这个类属于哪个工厂,使用id()表示这个类的具体类型。让我们将@Factory注解应用到这些类上吧:

     1 @Factory(type=MargheritaPizza.class, id="Margherita")
     2 public class MargheritaPizza implements Meal{
     3 
     4     @Override
     5     public float getPrice() {
     6         return 6.0f;
     7     }
     8 }
     9 
    10 @Factory(type=CalzonePizza.class, id="Calzone")
    11 public class CalzonePizza implements Meal{
    12 
    13     @Override
    14     public float getPrice() {
    15         return 8.5f;
    16     }
    17 }
    18 
    19 @Factory(type=Tiramisu.class, id="Tiramisu")
    20 public class Tiramisu implements Meal{
    21 
    22     @Override
    23     public float getPrice() {
    24         return 4.5f;
    25     }
    26 }

    你可能会问,我们是不是可以只将@Factory注解应用到Meal接口上?答案是不行,因为注解是不能被继承的。即在class X上有注解,class Y extends X,那么class Y是不会继承class X上的注解的。在我们编写处理器之前,需要明确几点规则:

    1. 只有类能够被@Factory注解,因为接口和虚类是不能通过new操作符实例化的。
    2. @Factory注解的类必须提供一个默认的无参构造函数。否则,我们不能实例化一个对象。
    3. @Factory注解的类必须直接继承或者间接继承type指定的类型。(或者实现它,如果type指定的是一个接口)
    4. @Factory注解的类中,具有相同的type类型的话,这些类就会被组织起来生成一个工厂类。工厂类以Factory作为后缀,例如:type=Meal.class将会生成MealFactory类。
    5. id的值只能是字符串,且在它的type组中必须是唯一的。

    注解处理器:

     1 public class FactoryProcessor extends AbstractProcessor {
     2 
     3     private Types typeUtils;
     4     private Elements elementUtils;
     5     private Filer filer;
     6     private Messager messager;
     7     private Map<String, FactoryGroupedClasses> factoryClasses = 
     8             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 boolean process(Set<? extends TypeElement> arg0,
    21             RoundEnvironment arg1) {
    22         ...
    23         return false;
    24     }
    25 
    26     @Override
    27     public Set<String> getSupportedAnnotationTypes() {
    28         Set<String> annotataions = new LinkedHashSet<String>();
    29         annotataions.add(Factory.class.getCanonicalName());
    30         return annotataions;
    31     }
    32 
    33     @Override
    34     public SourceVersion getSupportedSourceVersion() {
    35         return SourceVersion.latestSupported();
    36     }
    37 }

    getSupportedAnnotationTypes()方法中,我们指定@Factory注解将被这个处理器处理。

  • 相关阅读:
    HDU 1882 Strange Billboard(位运算)
    Codeforces Round #243 (Div. 2) A~C
    Codeforces Round #242 (Div. 2) A~C
    2014 微软 编程之美初赛第一场 题目3 : 活动中心
    2014年微软编程之美初赛第一场 第二题 树
    POJ 2318 TOYS && POJ 2398 Toy Storage(几何)
    Coder-Strike 2014
    POJ 1269 Intersecting Lines(几何)
    HDU 1883 Phone Cell (圆覆盖最多点)
    HDU 1429 胜利大逃亡(续)(三维BFS)
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/9020478.html
Copyright © 2020-2023  润新知