• APT和Javapoet的精彩联动


    APT

    APT,即注解处理器,是一种处理注解的工具。确切来说,它是javac的一个工具,用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,以生成.java文件作为输出。简单来说,就是在编译期通过注解生成.java文件。

    Element

    自定义注解处理器,需要继承AbstractProcessor类。对于AbstractProcessor来说,最重要的就是process方法,process方法处理的核心是Element对象。
    下面我们详细看下Element对象。

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package javax.lang.model.element;
    
    import java.lang.annotation.Annotation;
    import java.util.List;
    import java.util.Set;
    import javax.lang.model.AnnotatedConstruct;
    import javax.lang.model.type.TypeMirror;
    
    public interface Element extends AnnotatedConstruct {
        TypeMirror asType();//返回元素的类型,实际的对象类型。
    
        ElementKind getKind();//返回此元素的种类(即5种子类):包、类、接口、方法、字段等
    
        Set<Modifier> getModifiers();//返回此元素的修饰符,如public、static、final等关键字
    
        Name getSimpleName();//返回此元素的简单名称,如类名
    
        Element getEnclosingElement();
        //返回该元素的父元素;ExecutableElement的父级是TypeElemnt,而TypeElemnt的父级是PackageElment。
        //注意:包元素getEnclosingElement()返回是null
    
        List<? extends Element> getEnclosedElements();
        //返回该元素所包含的元素,相当于在当前元素向里解一层。比如当前元素类型是class,getEnclosingElements()可以获取该类所包含的所有成员变量和方法。当前是包元素是,返回该包下的所有类元素。
    
        boolean equals(Object var1);
    
        int hashCode();
    
        List<? extends AnnotationMirror> getAnnotationMirrors();
    
        <A extends Annotation> A getAnnotation(Class<A> var1);
        //返回此元素针对指定类型的注解。注解可以是继承的,也可以是直接存在于此元素上的。
    
        <R, P> R accept(ElementVisitor<R, P> var1, P var2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    除了上述的几个方法外,APT中还有以下几个方法比较常用

    属性名含义
    getSimpleName()获取名字,如果是类元素,不包含完整的包名路径
    getQualifiedName()获取全名,如果是类的话,包含完整的包名路径
    getParameters()获取方法的参数元素,每个元素是一个VariableElement
    getReturnType()获取方法元素的返回值
    getConstantValue()如果属性变量被final修饰,则可以使用该方法获取它的值

    Element有5个直接子类,它们分别代表一种特定类型的元素。

    类型含义
    TypeElement一个类或接口程序元素
    VariableElement一个字段、enum常量、方法或构造方法参数、局部变量或异常参数
    ExecutableElement某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
    PackageElement一个包程序元素
    TypeParameterElement一般类、接口、方法或构造方法元素的泛型参数

    也许,这么说太过于官方了,我们举个简单的例子,大家一看就明白

    package com.netease.apt; //PackageElement 包元素
    public class Main{//TypeElement 类元素
    	private int x: //VariableElement 属性元素
    	private Main{}// ExecutableElement 方法元素
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其中,TypeElement和VariableElement是最核心的两个Element。
    接下来,我们通过APT来实现一个类似于ButterKnife中的@BindView注解。通过对View变量的注解,实现对View的绑定。

    首先,我们新建一个java依赖库,专门来存放我们的自定义注解。

    在这里插入图片描述
    在这里插入图片描述

    接下来,我们创建一个apt-processor模块(也是一个java模块),同时该模块要依赖于apt-annotation模块以及auto-service第三方库。

    BrettBindViewProcesor继承AbstractProcessor类,并且需要在类上使用@AutoService(Processor.class)注解标记,表明当前类是一个注解处理器。
    接着需要我们创建文件src/main/resources/META-INF/services/javax.annotation.processing.Processor进行声明。文件里的内容就是我们自定义的注解处理器的包名和类名。在本例中如下所示:

    com.brett.apt_processor.BrettBindViewProcesor
    
    • 1

    AbstractProcessor类有几个重要的方法如下所示:

    方法名含义
    init初始化函数,可以得到ProcessingEnvironment对象。ProcessingEnvironment提供很多有用的工具类,如Elements、Types和Filer。
    getSupportedAnnotationTypes指定这个注解处理器是注册给哪个注解的,这里指定的是我们上面创建的注解@BrettBindView。
    getSupportedSourceVersion指定使用的Java版本,通常这里返回SourceVersion.latestSupported()。
    package com.brett.apt_processor;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.lang.model.element.PackageElement;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    
    /**
     * Created by Brett.li on 2022/7/3.
     */
    public class BrettClassCreateFactory {
        private String mBindingClassName;
        private String mPackageName;
        private TypeElement mTypeElement;
        private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
    
        BrettClassCreateFactory(Elements elements, TypeElement classElement) {
            this.mTypeElement = classElement;
            PackageElement packageElement = elements.getPackageOf(mTypeElement);
            String packageName = packageElement.getQualifiedName().toString();
            String className = mTypeElement.getSimpleName().toString();
            this.mPackageName = packageName;
            this.mBindingClassName = className + "_BrettViewBinding";
        }
    
        public void putElement(int id, VariableElement element) {
            mVariableElementMap.put(id, element);
        }
    
        public String generateJavaCode() {
            StringBuilder builder = new StringBuilder();
            builder.append("package ").append(mPackageName).append(";\n");
            builder.append('\n');
            builder.append("public class ").append(mBindingClassName);
            builder.append(" {\n");
    
            generateBindViewMethods(builder);
            builder.append('\n');
            builder.append("}\n");
            return builder.toString();
        }
    
        private void generateBindViewMethods(StringBuilder builder) {
            builder.append("\tpublic void bindView(");
            builder.append(mTypeElement.getQualifiedName());
            builder.append(" owner ) {\n");
            for (int id : mVariableElementMap.keySet()) {
                VariableElement element = mVariableElementMap.get(id);
                String viewName = element.getSimpleName().toString();
                String viewType = element.asType().toString();
                builder.append("\t\towner.");
                builder.append(viewName);
                builder.append(" = ");
                builder.append("(");
                builder.append(viewType);
                builder.append(")(((android.app.Activity)owner).findViewById( ");
                builder.append(id);
                builder.append("));\n");
            }
            builder.append("  }\n");
        }
    
        public String getProxyClassFullName() {
            return mPackageName + "." + mBindingClassName;
        }
    
        public TypeElement getTypeElement(){
            return mTypeElement;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    package com.brett.apt_processor;
    
    import com.brett.apt_annotation.BrettBindView;
    import com.google.auto.service.AutoService;
    
    import java.io.IOException;
    import java.io.Writer;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedHashSet;
    import java.util.Map;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    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.Element;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    import javax.tools.JavaFileObject;
    
    @AutoService(Processor.class)
    //@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则不用复写getSupportedSourceVersion方法
    //@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"})  //若使用了该注解,则不用复写getSupportedAnnotationTypes方法
    public class BrettBindViewProcesor extends AbstractProcessor {
        private Elements mElementUtils;
        private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            mElementUtils = processingEnv.getElementUtils();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            HashSet<String> supportTypes = new LinkedHashSet<>();
            supportTypes.add(BrettBindView.class.getCanonicalName());
            return supportTypes;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            mClassCreateFactoryMap.clear();
    
            //getElementsAnnotatedWith可以搜索到整个工程中所有使用了 BrettBindView 注解的元素
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BrettBindView.class);
    
            for (Element element : elements) {
                VariableElement variableElement = (VariableElement) element;//因为BrettBindView是作用在字段中,故可以强转为VariableElement
                TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();//拿到父element,VariableElement的父element为TypeElement
                String fullClassName = classElement.getQualifiedName().toString();
                BrettClassCreateFactory proxy = mClassCreateFactoryMap.get(fullClassName);
                if (proxy == null) {
                    proxy = new BrettClassCreateFactory(mElementUtils, classElement);
                    mClassCreateFactoryMap.put(fullClassName, proxy);
                }
                BrettBindView bindAnnotation = variableElement.getAnnotation(BrettBindView.class);//拿到被BrettBindView注解注释的控件
                int id = bindAnnotation.value();
                proxy.putElement(id, variableElement);
            }
            //创建java文件
            for (String key : mClassCreateFactoryMap.keySet()) {
                BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);
                try {
                    JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
                    Writer writer = jfo.openWriter();
                    writer.write(proxyInfo.generateJavaCode());
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;//返回true或false好像没有太大的区别
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    //apt-processor模块的gradle文件
    plugins {
        id 'java-library'
    }
    
    java {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    
    dependencies {
        implementation 'com.google.auto.service:auto-service:1.0.1'
        implementation project(':apt-annotation')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    最后,我们在app模块中引入上面两个模块即可。

    dependencies {
    
        implementation 'androidx.appcompat:appcompat:1.4.2'
        implementation 'com.google.android.material:material:1.6.1'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        implementation project(':apt-annotation')
        annotationProcessor project(':apt-processor')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.brett.myapplication;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    
    import com.brett.apt_annotation.BrettBindView;
    import com.brett.test1.Test1Activity;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MainActivity extends AppCompatActivity {
        @BrettBindView(R.id.btn_main)
        Button btnMain;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    //        btnMain = findViewById(R.id.btn_main);
            bindView(this);
    
            btnMain.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, Test1Activity.class);
                    startActivity(intent);
                }
            });
        }
    
        public static void bindView(Activity activity){
            Class clazz = activity.getClass();
            try {
                Class<?> bindViewClass = Class.forName(clazz.getName()+"_BrettViewBinding");
                Method method = bindViewClass.getMethod("bindView",activity.getClass());
                method.invoke(bindViewClass.newInstance(),activity);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    注意:编译时可能会报异常,可能是autoService跟java版本不匹配导致的,需要调试一下。
    当我们build之后,如果在build文件夹看到如下文件说明创建成功
    在这里插入图片描述

    Javapoet

    在上面的例子中,我们通过StringBuilder拼接生成了对应的java代码。但是,这种做法比较繁琐、容易出错,而且难以维护。所以,我们可以使用javapoet库来生成Java代码,javapoet引入了oop的思想。
    注意:使用javapoet生成代码未必就比使用原生方式好,如果复杂的代码生成,反而效率会低下。
    javapoet是一个开源项目,GitHub地址:https://github.com/square/javapoet
    我们在BrettClassCreateFactory类中添加如下代码:

    //使用javapoet创建java代码
        public TypeSpec generateJavaCodeWithJavapoet(){
            return TypeSpec.classBuilder(mBindingClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(generateMethodsWithJavapoet())
                    .build();
        }
    
        private MethodSpec generateMethodsWithJavapoet(){
            ClassName owner = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bindView")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(owner,"owner");
    
            for (int id : mVariableElementMap.keySet()) {
                VariableElement element = mVariableElementMap.get(id);
                String viewName = element.getSimpleName().toString();
                String viewType = element.asType().toString();
                methodBuilder.addCode("owner." + viewName + " = " + "(" + viewType + ")(((android.app.Activity)owner).findViewById( " + id + "));");
            }
            return methodBuilder.build();
        }
    
        public String getPackageName() {
            return mPackageName;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    package com.brett.apt_processor;
    
    import com.brett.apt_annotation.BrettBindView;
    import com.google.auto.service.AutoService;
    import com.squareup.javapoet.JavaFile;
    
    import java.io.IOException;
    import java.io.Writer;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedHashSet;
    import java.util.Map;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    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.Element;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.lang.model.util.Elements;
    import javax.tools.JavaFileObject;
    
    @AutoService(Processor.class)
    //@SupportedSourceVersion(SourceVersion.RELEASE_8) //若使用了该注解,则可以不用复写getSupportedSourceVersion方法
    //@SupportedAnnotationTypes({"com.brett.apt_annotation.BrettBindView"})  //若使用了该注解,则可以不用复写getSupportedAnnotationTypes方法
    public class BrettBindViewProcesor extends AbstractProcessor {
        private Elements mElementUtils;
        private Map<String, BrettClassCreateFactory> mClassCreateFactoryMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            mElementUtils = processingEnv.getElementUtils();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            HashSet<String> supportTypes = new LinkedHashSet<>();
            supportTypes.add(BrettBindView.class.getCanonicalName());
            return supportTypes;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //。。。。。
            //创建java文件
            for (String key : mClassCreateFactoryMap.keySet()) {
                BrettClassCreateFactory proxyInfo = mClassCreateFactoryMap.get(key);
                //使用原始方式
    //            try {
    //                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
    //                Writer writer = jfo.openWriter();
    //                writer.write(proxyInfo.generateJavaCode());
    //                writer.flush();
    //                writer.close();
    //            } catch (IOException e) {
    //                e.printStackTrace();
    //            }
                //使用javapoet方式
                JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCodeWithJavapoet()).build();
                try{
                    javaFile.writeTo(processingEnv.getFiler());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            return true;//返回true或false好像没有太大的区别
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    JavaPoet相关

    类对象说明
    MethodSpec代表一个构造函数或方法声明
    TypeSpec代表一个类,接口或枚举声明
    FieldSpec代表一个成员变量、字段声明
    JavaFile代表一个顶级类的java文件
    Parameterspec用来创建参数
    AnnotationSpec用来创建注解
    ClassName用来包装一个类
    TypeName类型,如在添加返回值类型是使用 TypeName . VoID

    最后,我们build下工程同样可以生成MainActivity_BrettViewBinding文件。

  • 相关阅读:
    内核Netfilter框架的原理及功能
    论文《Enhancing Hypergraph Neural Networks with Intent Disentanglement for SBR》阅读
    算法分析——大O标记法之时间复杂度
    STM32微控制器实现无人机智能导航与控制(内附资料)
    ​Excel如何转换成Word文档?教你如何实现转换
    Naive UI 文档地址
    关于ZooKeeper的一些面试题
    计组——I/O方式
    Ubuntu 安装 Docker Engine
    Linux第一个小程序——进度条
  • 原文地址:https://blog.csdn.net/qq_36828822/article/details/125589296