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

除了上述的几个方法外,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 方法元素
}

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

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

java 讲crt 转 pem java转cpp工具_android


java 讲crt 转 pem java转cpp工具_java 讲crt 转 pem_02

接下来,我们创建一个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

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;
    }
}
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;//return 如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们
    }
}
//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')
}

最后,我们在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')
}
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();
        }

    }
}

注意:编译时可能会报异常,可能是autoService跟java版本不匹配导致的,需要调试一下。

当我们build之后,如果在build文件夹看到如下文件说明创建成功

java 讲crt 转 pem java转cpp工具_java_03

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;
    }
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好像没有太大的区别
    }
}

JavaPoet相关

类对象

说明

MethodSpec

代表一个构造函数或方法声明

TypeSpec

代表一个类,接口或枚举声明

FieldSpec

代表一个成员变量、字段声明

JavaFile

代表一个顶级类的java文件

Parameterspec

用来创建参数

AnnotationSpec

用来创建注解

ClassName

用来包装一个类

TypeName

类型,如在添加返回值类型是使用 TypeName . VoID

最后,我们build下工程同样可以生成MainActivity_BrettViewBinding文件。
注意:如果在不同的模块中都引入了apt模块,有可能会生成多份同名的文件,必须要对生成的文件进行标识。