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 @Override16 public boolean process(Set<? extends TypeElement> annoations,17 RoundEnvironment env) {18 return false;19 }20 21 @Override22 public Set<String> getSupportedAnnotationTypes() {23 Set<String> annotataions = new LinkedHashSet<String>();24 annotataions.add("com.example.MyAnnotation");25 return annotataions;26 }27 28 @Override29 public SourceVersion getSupportedSourceVersion() {30 return SourceVersion.latestSupported();31 }32 33 @Override34 public synchronized void init(ProcessingEnvironment processingEnv) {35 super.init(processingEnv);36 }37 38 }
init(ProcessingEnvironment processingEnv)
:所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init()
,它会被注解处理工具调用,以ProcessingEnvironment
作为参数。ProcessingEnvironment 提供了一些实用的工具类Elements
,Types
和Filer
。我们在后面将会使用到它们。process(Set<? extends TypeElement> annoations, RoundEnvironment env)
:这类似于每个处理器的main()
方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment
参数,你可以查询被特定注解标注的元素(原文:you can query for elements annotated with a certain annotation )。后面我们将会看到详细内容。getSupportedAnnotationTypes()
:在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String
集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。getSupportedSourceVersion()
: 用来指定你使用的 java 版本。通常你应该返回SourceVersion.latestSupported()
。不过,如果你有足够的理由坚持用 java 6 的话,你也可以返回SourceVersion.RELEASE_6
。我建议使用SourceVersion.latestSupported()
。在 Java 7 中,你也可以使用注解的方式来替代重写getSupportedAnnotationTypes()
和getSupportedSourceVersion()
,如下所示:
@SupportedSourceVersion(value=SourceVersion.RELEASE_7) @SupportedAnnotationTypes({ // Set of full qullified annotation type names "com.example.MyAnnotation", "com.example.AnotherAnnotation" })public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { return false; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); } }
由于兼容性问题,特别是对于 android ,我建议重写getSupportedAnnotationTypes()
和 getSupportedSourceVersion()
,而不是使用 @SupportedAnnotationTypes
和 @SupportedSourceVersion
。
接下来你必须知道的事情是:注解处理器运行在它自己的 JVM 中。是的,你没看错。javac 启动了一个完整的 java 虚拟机来运行注解处理器。这意味着什么?你可以使用任何你在普通 java 程序中使用的东西。使用 guava
! 你可以使用依赖注入工具,比如dagger
或者任何其他你想使用的类库。但不要忘记,即使只是一个小小的处理器,你也应该注意使用高效的算法及设计模式,就像你在开发其他 java 程序中所做的一样。
3. 注册你的处理器
你可能会问 “怎样注册我的注解处理器到 javac ?”。你必须提供一个.jar
文件。就像其他 .jar 文件一样,你将你已经编译好的注解处理器打包到此文件中。并且,在你的 .jar 文件中,你必须打包一个特殊的文件javax.annotation.processing.Processor
到META-INF/services
目录下。因此你的 .jar 文件目录结构看起来就你这样:
MyProcess.jar -com -example -MyProcess.class -META-INF -services -javax.annotation.processing.Processor
javax.annotation.processing.Processor
文件的内容是一个列表,每一行是一个注解处理器的全称。例如:
com.example.MyProcess com.example.AnotherProcess
4. 例子:工厂模式
我们要解决的问题是:我们要实现一个 pizza 店,这个 pizza 店提供给顾客两种 pizza (Margherita 和 Calzone),还有甜点 Tiramisu(提拉米苏)。
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 @Override12 public float getPrice() {13 return 8.5f;14 }15 }16 public class Tiramisu implements Meal{17 @Override18 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 @Override14 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 @Override23 public float getPrice() {24 return 4.5f;25 }26 }
你可能会问,我们是不是可以只将@Factory
注解应用到Meal
接口上?答案是不行,因为注解是不能被继承的。即在class X
上有注解,class Y extends X
,那么class Y
是不会继承class X
上的注解的。在我们编写处理器之前,需要明确几点规则:
只有类能够被
@Factory
注解,因为接口和虚类是不能通过new
操作符实例化的。被
@Factory
注解的类必须提供一个默认的无参构造函数。否则,我们不能实例化一个对象。被
@Factory
注解的类必须直接继承或者间接继承type
指定的类型。(或者实现它,如果type
指定的是一个接口)被
@Factory
注解的类中,具有相同的type
类型的话,这些类就会被组织起来生成一个工厂类。工厂类以Factory
作为后缀,例如:type=Meal.class
将会生成MealFactory
类。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 @Override11 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 @Override20 public boolean process(Set<? extends TypeElement> arg0,21 RoundEnvironment arg1) {22 ...23 return false;24 }25 26 @Override27 public Set<String> getSupportedAnnotationTypes() {28 Set<String> annotataions = new LinkedHashSet<String>();29 annotataions.add(Factory.class.getCanonicalName());30 return annotataions;31 }32 33 @Override34 public SourceVersion getSupportedSourceVersion() {35 return SourceVersion.latestSupported();36 }37 }
在getSupportedAnnotationTypes()
方法中,我们指定@Factory
注解将被这个处理器处理。