美文网首页
Android 注解处理器

Android 注解处理器

作者: VanceKing | 来源:发表于2022-02-23 14:53 被阅读0次

    概述

    APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。
    通过在编译期间调用 javac -processor 命令可以掉起注解处理器。只能生成新的源文件而不能修改已经存在的源文件。

    Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类并实现 process() 方法来实现自己的注解解析逻辑。

    APT 的原理是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码。

    注解(Annotation)

    // java.lang.annotation
    /**
     * The common interface extended by all annotation types.
     */
    public interface Annotation {
        boolean equals(Object obj);
        int hashCode();
        String toString();
        Class<? extends Annotation> annotationType();
    }
    

    示例:

    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE, ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
    public @interface PrintMe {
        String value() default "";
    }
    

    @Retention 定义注解的生效范围,一共 3 种:

    • SOURCE,只作用于源码。可以用作 Lint、APT 这种工作在源码上的工具,会在编译环节被编译器主动舍弃。
    • CLASS(默认),作用于编译后的 Class 文件中,但不会在运行时被 JVM 加载。因为在编译环节中生效,如 class 编译成 dex 的过程中,就可以通过 transform 等手段加以利用。
    • RUNTIME,作用于运行时,生命周期最长,可用于反射。

    @Target 定义注解作用的对象,常用的有:

    • FIELD 成员变量,包含枚举类型的成员值。
    • METHOD 在方法上是修饰方法的返回值。
    • PARAMETER 修饰方法的参数。
    • TYPE 修饰类或接口。

    @Inhertited 用来表明注解的继承特性。注解本身并不具备继承性,这里所说的继承指的是注解所标记的类或接口的继承关系。举个例子,注解@Foo 被定义为:

    @Target(TYPE)
    @Rentention(RUNTIME)
    @Inhertited
    public @interface Foo() {
        int value() default -1;
    }
    
    @Foo(value = 2)
    class A {}
    
    class B extends A {}
    
    public static void main(String[] args) {
        Foo annotation = B.class.getAnnotation(Foo.class);
        //如果去除 Foo 上的 @Inherited 注解,annotation == null
        System.out.println("value: " + annotation.value());
    }
    

    元素(Element)

    Represents a program element such as a package, class, or method. Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine).
    Element 用于代表程序的一个元素,这个元素可以是包、类、接口、变量、方法等。

    PackageElement

    Represents a package program element. Provides access to information about the package and its members.
    表示一个包程序元素。提供对有关包及其成员的信息的访问。

    ExecutableElement

    Represents a method, constructor, or initializer (static or instance) of a class or interface, including annotation type elements.
    表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。

    TypeElement

    Represents a class or interface program element. Provides access to information about the type and its members. Note that an enum type is a kind of class and an annotation type is a kind of interface.
    表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。

    VariableElement

    Represents a field, enum constant, method or constructor parameter, local variable, resource variable, or exception parameter.
    表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

    package com.java.annotation;  // PackageElement
    
    public class Circle {  // TypeElement
    
        private int i; // VariableElement
        private Triangle triangle; // VariableElement
    
        // ExecuteableElement
        public Circle() {}
    
        // ExecuteableElement
        public void draw(String s) {// s: VariableElement
            System.out.println(s);
        }
    }
    

    kapt

    kapt 也是 APT 工具的一种,使用 Kotlin 开发 Android 应用时,要在预编译阶段处理注解必须使用 kotlin-kapt 而不能使用 annotationProcessor,kotlin-kapt 不是 Android Gradle 内置插件需要额外引入。

    apply plugin: 'kotlin-kapt'
    kapt "com.android.databinding:compiler:$android_plugin_version"
    

    AbstractProcessor 类介绍

    apt
    Java Compilation Process with Annotation Processing
    // javax.annotation.processing.Processor.java
    public interface Processor {
        // 命令行选项
        Set<String> getSupportedOptions();
        // 支持的注解
        Set<String> getSupportedAnnotationTypes();
        // 支持的 Java 版本,一般返回 SourceVersion.latestSupported()
        SourceVersion getSupportedSourceVersion();
        // 注解处理器初始化
        void init(ProcessingEnvironment processingEnv);
        // 处理过程
        boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
        Iterable<? extends Completion> getCompletions(Element element,
                AnnotationMirror annotation, ExecutableElement member, String userText);
    }
    
    • ProcessingEnvironment: 代表了注解处理器框架提供的一个上下文环境,要创建新的代码或者向编译器输出信息或者获取其他工具类等都需要用到这个实例变量。
    • RoundEnvironment: 提供了访问到当前这个 Round 中语法树节点的功能。
    // javax.annotation.processing.Processor.java
    public abstract class AbstractProcessor implements Processor {
        /**
         * Processing environment providing by the tool framework.
         */
        protected ProcessingEnvironment processingEnv;
        private boolean initialized = false;
    
        protected AbstractProcessor() {}
    
        public Set<String> getSupportedOptions() {
            SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
            if  (so == null)
                return Collections.emptySet();
            else
                return arrayToSet(so.value());
        }
    
        public Set<String> getSupportedAnnotationTypes() {
            SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
            if  (sat == null) {
                if (isInitialized())
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                    "No SupportedAnnotationTypes annotation " + "found on " + this.getClass().getName() +
                    ", returning an empty set.");
                return Collections.emptySet();
            }
            else
                return arrayToSet(sat.value());
        }
    
        public SourceVersion getSupportedSourceVersion() {
            SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
            SourceVersion sv = null;
            if (ssv == null) {
                sv = SourceVersion.RELEASE_6;
                if (isInitialized())
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                        "No SupportedSourceVersion annotation " +
                        "found on " + this.getClass().getName() +
                        ", returning " + sv + ".");
            } else
                sv = ssv.value();
            return sv;
        }
    
        public synchronized void init(ProcessingEnvironment processingEnv) {
            if (initialized)
                throw new IllegalStateException("Cannot call init more than once.");
            Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
    
            this.processingEnv = processingEnv;
            initialized = true;
        }
    
        public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);
    
        public Iterable<? extends Completion> getCompletions(Element element,
                                                             AnnotationMirror annotation,
                                                             ExecutableElement member,
                                                             String userText) {
            return Collections.emptyList();
        }
    
        protected synchronized boolean isInitialized() {
            return initialized;
        }
    
        private static Set<String> arrayToSet(String[] array) {
            assert array != null;
            Set<String> set = new HashSet<String>(array.length);
            for (String s : array)
                set.add(s);
            return Collections.unmodifiableSet(set);
        }
    }
    

    init(ProcessingEnvironment env)

    初始化方法,会被处理器调用。ProcessingEnvironment 包含了很多有用的工具类,列如 Elements、Types、Filter 等。

    • getElementUtils():处理 Element 的工具类,用于获取程序的元素,例如包、类、方法。
    • getTypeUtils():处理 TypeMirror 的工具类,用于取类信息
    • getFiler():文件工具
    • getMessager():错误处理工具

    getSupportedAnnotationTypes()

    指定处理器能够处理的注解类型。

    @Override public Set<String> getSupportedAnnotationTypes() {
        Set<String> supportedTypes = new HashSet<>(1);
        supportedTypes.add(Print.class.getCanonicalName());
        return supportedTypes;
    }
    

    getSupportedSourceVersion()

    指定处理器使用的 Java 版本。一般是 SourceVersion.latestSupported()

    process()

    抽象方法,实现注解处理器的具体逻辑。获取被注解的元素,生成 Java 源代码等信息。

    @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
        Set<? extends Element> elements = env.getElementsAnnotatedWith(PrintMe.class);
        //由于编译器的输出无法打印到控制台,使用 javapoet 库把需要输出的信息写入到一个新的类中
        for (Element element : elements) {
            PrintMe annotation = element.getAnnotation(PrintMe.class);
            ...
        }
        return false;
    }
    

    返回值表示注解是否由当前 Processor 处理。如果返回 true,则这些注解由此注解来处理,后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此 Processor 中处理并,那么后续 Processor 可以继续处理它们。

    Java SPI

    SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。

    java spi
    Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制。

    为某个接口寻找服务实现的机制。

    使用 Java SPI,需要遵循如下约定:

    • 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
    • 接口实现类所在的 jar 包放在主程序的 classpath 中;
    • 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM;
    • SPI 的实现类必须携带一个不带参数的构造方法;

    Java 项目示例:

    └─src
        ├─com
        │  └─spi
        │          IInterface.java
        │          ImplA.java
        │          ImplB.java
        │          Main.java
        │
        └─resources
            └─META-INF
                └─services
                        com.spi.IInterface
    

    com.spi.IInterface 文件内容为:

    com.spi.ImplA
    com.spi.ImplB
    
    public interface IInterface {
        String name();
    }
    
    public class ImplA implements IInterface {
        @Override
        public String name() {
            return "ImplA";
        }
    }
    
    public class ImplB implements IInterface {
        @Override
        public String name() {
            return "ImplB";
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            ServiceLoader<IInterface> serviceLoader = ServiceLoader.load(IInterface.class);
            for (IInterface service : serviceLoader) {
                System.out.println(service.name());//输出: ImplA ImplB
            }
        }
    }
    
    //文件 com.spi.IInterface 包含如下内容
    com.spi.ImplA
    com.spi.ImplB
    

    源码分析

    public final class ServiceLoader<S> implements Iterable<S> {
        private static final String PREFIX = "META-INF/services/";
        // The class or interface representing the service being loaded
        private final Class<S> service;
        // The class loader used to locate, load, and instantiate providers
        private final ClassLoader loader;
        // The access control context taken when the ServiceLoader is created
        private final AccessControlContext acc;
        // Cached providers, in instantiation order
        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
        // The current lazy-lookup iterator
        private LazyIterator lookupIterator;
    
        public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    
        private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            reload();
        }
    
        private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) {
            String ln = r.readLine();
            if (ln == null) {
                return -1;
            }
            int ci = ln.indexOf('#');
            if (ci >= 0) ln = ln.substring(0, ci);
            ln = ln.trim();
            int n = ln.length();
            if (n != 0) {
                // 检查文件,不符合抛异常
                if (!providers.containsKey(ln) && !names.contains(ln))
                    names.add(ln);
            }
            return lc + 1;
        }
    
        private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
            ArrayList<String> names = new ArrayList<>();
            InputStream in = u.openStream();
            BufferedReader r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
            return names.iterator();
        }
    
        public Iterator<S> iterator() {
            return new Iterator<S>() {
                public boolean hasNext() {
                    return lookupIterator.hasNext();
                }
    
                public S next() {
                    return lookupIterator.next();
                }
    
            };
        }
    
        public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
            return new ServiceLoader<>(service, loader);
        }
    
        // Private inner class implementing fully-lazy provider lookup
        private class LazyIterator implements Iterator<S> {
            private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
                        String fullName = PREFIX + service.getName();
                        if (loader == null)
                            configs = ClassLoader.getSystemResources(fullName);
                        else
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();
                return true;
            }
    
            private S nextService() {
                String cn = nextName;
                nextName = null;
                Class<?> c = Class.forName(cn, false, loader);
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            }
        }
    }
    
    1. ServiceLoader.load() 方法创建 ServiceLoader 对象;
    2. 通过 foreach 遍历 ServiceLoader 时调用 ServiceLoader#iterator() 方法;
    3. 实际是通过私有内部类 LazyIterator 代理遍历解析 Service;
    4. 解析过程是找到 META-INF 目录下的接口文件,打开该文件流,一行一行的读取,获取实现类字符串,保存到 names 集合中;
    5. 遍历 names 集合,通过反射实例化实例,并保存在 providers 缓存中;

    如果使用 AutoService 需要添加如下依赖:

    dependencies {
        implementation 'com.google.auto.service:auto-service:1.0'
        annotationProcessor 'com.google.auto.service:auto-service:1.0'
    }
    

    相关库

    JavaPoet

    JavaPoet 是 square 开源的 Java 代码生成框架,可以很方便地通过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码。

    依赖: com.squareup:javapoet:1.11.1

    auto-service

    auto-service 是由 Google 开源的注解处理器。

    依赖: com.google.auto.service:auto-service:1.0

    参考

    [1] Processor
    [2] Annotation Types - jls
    [3] 深入理解 Java 注解处理器 APT
    [4] 使用 Google 开源库 AutoService 进行组件化开发

    相关文章

      网友评论

          本文标题:Android 注解处理器

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