1. 一些基本概念
在开始之前, 我们需要声明一件重要的事情是: 我们不是在讨论在运行时通过反射机制运行处理的注解, 而是在讨论在编译时处理的注解.
注解处理器是 javac 自带的一个工具, 用来在编译时期扫描处理注解信息. 你可以为某些注解注册自己的注解处理器. 这里, 我假设你已经了解什么是注解及如何自定义注解. 如果你还未了解注解的话, 可以查看官方文档 http://docs.oracle.com/javase/tutorial/java/annotations/index.html . 注解处理器在 Java 5 的时候就已经存在了, 但直到 Java 6 (发布于 2006 看十二月)的时候才有可用的 API. 过了一段时间 java 的使用者们才意识到注解处理器的强大. 所以最近几年它才开始流行.
一个特定注解的处理器以 java 源代码 (或者已编译的字节码) 作为输入, 然后生成一些文件 (通常是. java 文件) 作为输出. 那意味着什么呢? 你可以生成 java 代码! 这些 java 代码在生成的. java 文件中. 因此你不能改变已经存在的 java 类, 例如添加一个方法. 这些生成的 java 文件跟其他手动编写的 java 源代码一样, 将会被 javac 编译.
Annotation processing 是在编译阶段执行的, 它的原理就是读入 Java 源代码, 解析注解, 然后生成新的 Java 代码. 新生成的 Java 代码最后被编译成 Java 字节码, 注解解析器 (Annotation Processor) 不能改变读入的 Java 类, 比如不能加入或删除 Java 方法.
2. AbstractProcessor
让我们来看一下处理器的 API. 所有的处理器都继承了 AbstractProcessor, 如下所示:
- package com.example;
- import java.util.LinkedHashSet;
- import java.util.Set;
- import javax.annotation.processing.AbstractProcessor;
- import javax.annotation.processing.ProcessingEnvironment;
- import javax.annotation.processing.RoundEnvironment;
- import javax.annotation.processing.SupportedAnnotationTypes;
- import javax.annotation.processing.SupportedSourceVersion;
- import javax.lang.model.SourceVersion;
- import javax.lang.model.element.TypeElement;
- public class MyProcessor extends AbstractProcessor {
- @Override
- public boolean process(Set<? extends TypeElement> annoations,
- RoundEnvironment env) {
- return false;
- }
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- Set<String> annotataions = new LinkedHashSet<String>();
- annotataions.add("com.example.MyAnnotation");
- return annotataions;
- }
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latestSupported();
- }
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- }
- }
- init(ProcessingEnvironment processingEnv)
: 所有的注解处理器类都必须有一个无参构造函数. 然而, 有一个特殊的方法 init(), 它会被注解处理工具调用, 以
ProcessingEnvironment
作为参数. ProcessingEnvironment 提供了一些实用的工具类 Elements, Types 和 Filer. 我们在后面将会使用到它们.
process(Set<? extends TypeElement> annoations, RoundEnvironment env)
: 这类似于每个处理器的 main()方法. 你可以在这个方法里面编码实现扫描, 处理注解, 生成 java 文件. 使用 RoundEnvironment 参数, 你可以查询被特定注解标注的元素(原文: you can query for elements annotated with a certain annotation ). 后面我们将会看到详细内容.
getSupportedAnnotationTypes()
: 在这个方法里面你必须指定哪些注解应该被注解处理器注册. 注意, 它的返回值是一个 String 集合, 包含了你的注解处理器想要处理的注解类型的全称. 换句话说, 你在这里定义你的注解处理器要处理哪些注解.
getSupportedSourceVersion()
: 用来指定你使用的 java 版本. 通常你应该返回
SourceVersion.latestSupported()
. 不过, 如果你有足够的理由坚持用 java 6 的话, 你也可以返回
SourceVersion.RELEASE_6
. 我建议使用
SourceVersion.latestSupported()
. 在 Java 7 中, 你也可以使用注解的方式来替代重写
getSupportedAnnotationTypes()
和
getSupportedSourceVersion()
, 如下所示:
- @SupportedSourceVersion(value=SourceVersion.RELEASE_7)
- @SupportedAnnotationTypes({
- // Set of full qullified annotation type names
- "com.example.MyAnnotation",
- "com.example.AnotherAnnotation"
- })
- public class MyProcessor extends AbstractProcessor {
- @Override
- public boolean process(Set<? extends TypeElement> annoations,
- RoundEnvironment env) {
- return false;
- }
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- }
- }
由于兼容性问题, 特别是对于 android , 我建议重写
getSupportedAnnotationTypes()
和
getSupportedSourceVersion()
, 而不是使用
@SupportedAnnotationTypes
和
- @SupportedSourceVersion
- .
接下来你必须知道的事情是: 注解处理器运行在它自己的 JVM 中. 是的, 你没看错. javac 启动了一个完整的 java 虚拟机来运行注解处理器. 这意味着什么? 你可以使用任何你在普通 java 程序中使用的东西. 使用 guava! 你可以使用依赖注入工具, 比如 dagger 或者任何其他你想使用的类库. 但不要忘记, 即使只是一个小小的处理器, 你也应该注意使用高效的算法及设计模式, 就像你在开发其他 java 程序中所做的一样.
3. 注册你的处理器
你可能会问 "怎样注册我的注解处理器到 javac ?". 你必须提供一个. jar 文件. 就像其他 .jar 文件一样, 你将你已经编译好的注解处理器打包到此文件中. 并且, 在你的 .jar 文件中, 你必须打包一个特殊的文件
javax.annotation.processing.Processor
到 META-INF/services 目录下. 因此你的 .jar 文件目录结构看起来就你这样:
- MyProcess.jar
- -com
- -example
- -MyProcess.class
- -META-INF
- -services
- -javax.annotation.processing.Processor
- javax.annotation.processing.Processor
文件的内容是一个列表, 每一行是一个注解处理器的全称. 例如:
- com.example.MyProcess
- com.example.AnotherProcess
4. 例子: 工厂模式
我们要解决的问题是: 我们要实现一个 pizza 店, 这个 pizza 店提供给顾客两种 pizza (Margherita 和 Calzone), 还有甜点 Tiramisu(提拉米苏).
- public interface Meal {
- public float getPrice();
- }
- public class MargheritaPizza implements Meal{
- @Override
- public float getPrice() {
- return 6.0f;
- }
- }
- public class CalzonePizza implements Meal{
- @Override
- public float getPrice() {
- return 8.5f;
- }
- }
- public class Tiramisu implements Meal{
- @Override
- public float getPrice() {
- return 4.5f;
- }
- }
- public class PizzaStore {
- public Meal order(String mealName) {
- if (null == mealName) {
- throw new IllegalArgumentException("name of meal is null!");
- }
- if ("Margherita".equals(mealName)) {
- return new MargheritaPizza();
- }
- if ("Calzone".equals(mealName)) {
- return new CalzonePizza();
- }
- if ("Tiramisu".equals(mealName)) {
- return new Tiramisu();
- }
- throw new IllegalArgumentException("Unknown meal'" + mealName + "'");
- }
- private static String readConsole() {
- Scanner scanner = new Scanner(System.in);
- String meal = scanner.nextLine();
- scanner.close();
- return meal;
- }
- public static void main(String[] args) {
- System.out.println("welcome to pizza store");
- PizzaStore pizzaStore = new PizzaStore();
- Meal meal = pizzaStore.order(readConsole());
- System.out.println("Bill:$" + meal.getPrice());
- }
- }
正如你所见, 在 order()方法中, 我们有许多 if 条件判断语句. 并且, 如果我们添加一种新的 pizza 的话, 我们就得添加一个新的 if 条件判断. 但是等一下, 使用注解处理器和工厂模式, 我们可以让一个注解处理器生成这些 if 语句. 如此一来, 我们想要的代码就像这样子:
- public class PizzaStore {
- private MealFactory factory = new MealFactory();
- public Meal order(String mealName) {
- return factory.create(mealName);
- }
- private static String readConsole() {
- Scanner scanner = new Scanner(System.in);
- String meal = scanner.nextLine();
- scanner.close();
- return meal;
- }
- public static void main(String[] args) {
- System.out.println("welcome to pizza store");
- PizzaStore pizzaStore = new PizzaStore();
- Meal meal = pizzaStore.order(readConsole());
- System.out.println("Bill:$" + meal.getPrice());
- }
- }
- public class MealFactory {
- public Meal create(String id) {
- if (id == null) {
- throw new IllegalArgumentException("id is null!");
- }
- if ("Calzone".equals(id)) {
- return new CalzonePizza();
- }
- if ("Tiramisu".equals(id)) {
- return new Tiramisu();
- }
- if ("Margherita".equals(id)) {
- return new MargheritaPizza();
- }
- throw new IllegalArgumentException("Unknown id =" + id);
- }
- }
- 5. @Factory Annotation
能猜到么, 我们打算使用注解处理器生成 MealFactory 类. 更一般的说, 我们想要提供一个注解和一个处理器用来生成工厂类.
让我们看一下 @Factory 注解:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.CLASS)
- public @interface Factory {
- /**
- * The name of the factory
- */
- Class<?> type();
- /**
- * The identifier for determining which item should be instantiated
- */
- String id();
- }
思想是这样的: 我们注解那些食物类, 使用 type()表示这个类属于哪个工厂, 使用 id()表示这个类的具体类型. 让我们将 @Factory 注解应用到这些类上吧:
- @Factory(type=MargheritaPizza.class, id="Margherita")
- public class MargheritaPizza implements Meal{
- @Override
- public float getPrice() {
- return 6.0f;
- }
- }
- @Factory(type=CalzonePizza.class, id="Calzone")
- public class CalzonePizza implements Meal{
- @Override
- public float getPrice() {
- return 8.5f;
- }
- }
- @Factory(type=Tiramisu.class, id="Tiramisu")
- public class Tiramisu implements Meal{
- @Override
- public float getPrice() {
- return 4.5f;
- }
- }
你可能会问, 我们是不是可以只将 @Factory 注解应用到 Meal 接口上? 答案是不行, 因为注解是不能被继承的. 即在 class X 上有注解, class Y extends X, 那么 class Y 是不会继承 class X 上的注解的. 在我们编写处理器之前, 需要明确几点规则:
只有类能够被 @Factory 注解, 因为接口和虚类是不能通过 new 操作符实例化的.
被 @Factory 注解的类必须提供一个默认的无参构造函数. 否则, 我们不能实例化一个对象.
被 @Factory 注解的类必须直接继承或者间接继承 type 指定的类型.(或者实现它, 如果 type 指定的是一个接口)
被 @Factory 注解的类中, 具有相同的 type 类型的话, 这些类就会被组织起来生成一个工厂类. 工厂类以 Factory 作为后缀, 例如: type=Meal.class 将会生成 MealFactory 类.
id 的值只能是字符串, 且在它的 type 组中必须是唯一的.
注解处理器:
- public class FactoryProcessor extends AbstractProcessor {
- private Types typeUtils;
- private Elements elementUtils;
- private Filer filer;
- private Messager messager;
- private Map<String, FactoryGroupedClasses> factoryClasses =
- new LinkedHashMap<String, FactoryGroupedClasses>();
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- typeUtils = processingEnv.getTypeUtils();
- elementUtils = processingEnv.getElementUtils();
- filer = processingEnv.getFiler();
- messager = processingEnv.getMessager();
- }
- @Override
- public boolean process(Set<? extends TypeElement> arg0,
- RoundEnvironment arg1) {
- ...
- return false;
- }
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- Set<String> annotataions = new LinkedHashSet<String>();
- annotataions.add(Factory.class.getCanonicalName());
- return annotataions;
- }
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latestSupported();
- }
- }
在
getSupportedAnnotationTypes()
方法中, 我们指定 @Factory 注解将被这个处理器处理.
来源: https://www.cnblogs.com/ganchuanpu/p/9020478.html