美文网首页Android开发经验谈
那些高端、优雅的注解是怎么实现的<4> -- 使用Annotai

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

作者: ifjgm | 来源:发表于2019-10-12 22:07 被阅读0次

    概述

    注解的解析可以通过反射,但反射的性能较低。所以在移动平台上,如安卓端使用,那是得不偿失的。那么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 地址

    相关文章

      网友评论

        本文标题:那些高端、优雅的注解是怎么实现的<4> -- 使用Annotai

        本文链接:https://www.haomeiwen.com/subject/fwbcmctx.html