那些高端、优雅的注解是怎么实现的<4> -- 使用Annotaion Processing Tool 解析注解

概述

注解的解析可以通过反射,但反射的性能较低。所以在移动平台上,如安卓端使用,那是得不偿失的。那么Android 端如何解析注解的呢?Android 端使用 apt 解析注解。然后使用自动生成的代码实现需要的逻辑。

自定义注解系列文章

APT 工具

APT(annotation processing tool)是一个命令行工具, 它对源代码文件进行检测找出其中的annotation后,使用annotation processors来处理annotation

处理过程

annotation processors处理annotation的基本过程如下

  • APT运行annotation processors根据提供的源文件中的 annotation 生成源代码文件和其它的文件(文件具体内容由annotation processors的编写者决定)
  • 接着APT将生成的源代码文件和提供的源文件进行编译生成类文件。

自定义 annotation processor

APT 运行 annotation processors根据源文件中的 annotation 生成源代码文件和其它的文件。那么如何定义 annotation processor 从而生成我们需要的代码,来实现自己逻辑就是问题的关键点了。自定义 annotation processors需要继承
AbstractProcessor
现在我们逐一说下 AbstractProcessor中对我们来说比较重要的方法

一:init(ProcessingEnvironment processingEnv) 方法

初始化操作的方法,RoundEnvironment会提供很多有用的工具类Elements、Types和Filer等。这些工具类可以简化我们后续自定义逻辑中的逻辑和代码。

二:process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)方法

这相当于每个处理器的主函数main()。在该方法中去扫描、评估、处理以及生成Java文件。

  • roundEnv.getElementsAnnotatedWith(Factory.class))
    返回所有被注解了@Factory的元素的列表,所有元素列表。也就是包括 类、包、方法、变量等。所以element 是如此重要的一个概念。
element 的概念

表示一个程序元素,比如包、类或者方法。每个元素都表示一个静态的语言级构造(不表示虚拟机的运行时构造)。 元素应该使用equals(Object)方法进行比较。不保证总是使用相同的对象表示某个特定的元素。 要实现基于Element对象类的操作,可以使用ElementVisitor或者使用getKind()方法的结果。使用instanceof确定此建模层次结构中某一对象的有效类未必可靠,因为一个实现可以选择让单个对象实现多个Element子接口。
如下的这个类就包含多种element

public class Foo {        // TypeElement 类型元素

    private int a;      // VariableElement 变量元素
    private Foo other;  // VariableElement 变量元素

    public Foo() { // ExecuteableElement 可执行元素
    }

    public void setA(int newA ) { //   newA  代表是一个 TypeElement)
    }
}

再来看下 Element 的源码,感谢一个努力的码农的分享。

public interface Element extends javax.lang.model.AnnotatedConstruct {
    /**
     * 返回该元素定义的类型。
     * 泛型元素定义了一系列类型,而不仅仅是一个类型。如果这是一个泛型元素,则返回一个原型
     * 类型。这是元素在对应于它自己的正式类型参数的类型变量上的调用。例如,对于泛型类元素
     * C<N extends Number>,返回参数化类型C<N>。类型实用程序接口有更一般的方法来获取元
     * 素定义的所有类型的范围。 
     */
    TypeMirror asType();

    /**
     * 返回该元素的类型
     */
    ElementKind getKind();

    /**
     * 返回该元素的修饰符,包括注解.
     * 隐式修饰符也包含,比如接口方法中的public和static
     */
    Set<Modifier> getModifiers();

    /**
     * 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。
     * 举例,java.util.Set<E>的简单名称是Set.
     * 如果该元素代表的是未命名包,则返回一个空 Name.
     * 如果代表的是构造器,则返回<init>所对应的Name.如果代表的是静态代码块,则返回的是<clinit>
     * 如果代表的是匿名类或者是初始代码块,则返回一个空 Name.
     */
    Name getSimpleName();

    /**
     * 返回包围该元素的最内层的元素.
     * 如果这个元素的声明紧接在另一个元素的声明中,则返回另一个元素。
     * 如果这是顶级类型,则返回其包。
     * 如果这是一个包,则返回null。
     * 如果这是类型参数,则返回类型参数的泛型元素。
     * 如果这是一个方法或构造函数参数,则返回声明该参数的可执行元素。
     */
    Element getEnclosingElement();

    /**
     * 返回该元素所包含的元素.
     * 类或接口被认为包含了它直接声明的字段、方法、构造函数和成员类型.包直接包含了顶级类和接
     * 口,但不包含其子包。其他类型的元素目前不被认为包含任何元素;然而,随着这个API或编程语
     * 言的发展,这些元素可能会改变
     */
    List<? extends Element> getEnclosedElements();

    /**
     * 当给定的参数和当前类代表同一个元素时返回true,否则,返回false.
     * 注意,元素的标识涉及不能直接从元素的方法中访问的隐式状态,包括关于不相关类型的存在的
     * 状态。即使“同一个”元素正在被建模,由这些接口的不同实现创建的元素对象也不应该期望相
     * 等;这类似于通过不同的类加载器加载的同一个类文件的Class对象是不同的
     *
     */
    @Override
    boolean equals(Object obj);

    /**
     * 基于Object.hashCode
     */
    @Override
    int hashCode();


    /**
     * 获得直接声明在该元素上的注解
     * 如果要获得继承的注解,使用Elements#getAllAnnotationMirrors(Element)方法.
     */
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();


    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);


    <R, P> R accept(ElementVisitor<R, P> v, P p);
}

TypeElement

TypeElement 表示一个类或接口程序元素(如上面的class Foo)。提供对有关类型及其成员的信息的访问。 而 DeclaredType 表示申明类型,你申明的是一个接口还是一个类等等,它也可以拥有泛型参数。这种区别对于一般的类型是最明显的,对于这些类型,单个元素可以定义一系列完整的类型。 例如,元素java.util.Set对应于参数化类型java.util.Set<String>java.util.Set<Number>(以及其他许多类型),还对应于原始类型java.util.Set。这块确实很绕,不过我们用一用就知道大概什么意思了。如果你还想继续深入研究,戳 这里

三:getSupportedSourceVersion()方法

用来指定你使用的java版本。通常这里会直接放SourceVersion.latestSupported()即可。

四:getSupportedAnnotationTypes():

这里你必须指定,该注解器解析器是注册给哪个注解的。

注意

从jdk 1.7开始,可以使用如下注解来代替getSupporedAnnotationTypes()getSupportedSourceVersion()方法:

@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
   // 合法注解全名的集合
 })

举个例子

有了上面的知识点 ,我们就可以开始我们的自定义注解之旅了。

第一:需求

假设有一个披萨店,他们有如如下的披萨。MargheritaPizzaCalzonePizzaTiramisu每个产品具有不同的价格。

第二:一般的实现方式

定义产品

因为后续可能会有其他新的产品陆续上线。所以我们定一个接口 Meal.如下

public interface Meal {  
  public float getPrice();
}

然后 MargheritaPizzaCalzonePizzaTiramisu 三个产品类的定义如下。

public class CalzonePizza implements Meal {
    @Override
    public float getPrice() {
        return 8.5f;
    }
}
public class MargheritaPizza implements Meal {
    @Override
    public float getPrice() {
        return 6.0f;
    }
}
public class Tiramisu implements Meal {
    @Override
    public float getPrice() {
        return 4.5f;
    }
}
定义披萨店
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 else 语句。虽然功能是实现了,但每次增加新品,都需要修改order方法,又在那长长的if else 语句中添加新的判断。首先这无形中增加了我们的维护成本,也不够美观。当然我们可以使用工厂模式,对它进行封装,这样看起来就好了很多。

使用工厂模式简化代码

定义 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);
  }
}


定义披萨店

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

现在披萨店的代码看起来简洁了很多,但是长长的if else 语句仍然存在(只是转移到了工厂类里面)。这些是很机械性的代码,如果有几百个品种,那可以写到怀疑人生了。所以有请我们的注解,下一篇我们用自定义注解自动生成MealFactory的代码。github 地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值