我的作品 xiuyuantech 博客 - 注解处理器 APT (Annotation Processing Tool)

jason2020(jason) · 2025年12月12日 · 12 次阅读

xiuyuantech 博客: https://xiuyuantech.github.io/

注解处理器是一种处理注解的工具,确切的说它是 javac 的一个工具,它用来在编译时扫描和处理注解。 注解处理器以 Java 代码 (或者编译过的字节码) 作为输入,生成.java 文件作为输出,减少手动的代码输入。 简单来说就是在编译期,通过注解生成.java 文件。比如我们经常用的轮子 Dagger2, ButterKnife, EventBus 都在用,我们要紧跟潮流来看看 APT 技术的来龙去脉。

实现方式 创建 Android Module 命名为 app 创建 Java library Module 命名为 apt-annotation 创建 Java library Module 命名为 apt-processor 依赖 apt-annotation 创建 Android library Module 命名为 apt-library 依赖 apt-annotation、auto-service 可选 compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar())

注意不可都放在一个 Module 中,放在一个 Module 中不会生效!

功能主要分为三个部分

apt-annotation:存放自定义注解 apt-processor:注解处理器,根据 apt-annotation 中的注解,在编译期生成 xxx.java 代码 apt-library:工具类,实现业务逻辑的绑定。

结构图

apt-annotation (自定义注解)

@Retention(RetentionPolicy.CLASS):表示编译时注解 @Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)

@Retention: 定义被保留的时间长短 RetentionPoicy.SOURCE、RetentionPoicy.CLASS、RetentionPoicy.RUNTIME @Target: 定义所修饰的对象范围 TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE 等

apt-processor (注解处理器)

添加依赖

dependencies {
    implementation project(':apt-annotation')
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) //optional
}

自定义处理器 BindViewProcessor

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    private Messager messager;
    private Trees trees;
    private TreeMaker maker;
    private Name.Table names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        trees = Trees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        maker = TreeMaker.instance(context);
        names = Names.instance(context).table;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            Set<? extends Element> elements = roundEnv.getRootElements();
            for (Element each : elements) {
                if (each.getKind() == ElementKind.CLASS) {
                    JCTree tree = (JCTree) trees.getTree(each);
                    if (tree != null) {
                        if (tree instanceof JCTree.JCClassDecl) {
                            if (((JCTree.JCClassDecl) tree).name.toString().equals(
                                    "WebAppInterface")) {
                                tree.accept(new Insert(messager, maker, names));
                                break;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    class Insert extends TreeTranslator {
        private Messager messager;
        private TreeMaker maker;
        private Name.Table names;

        public Insert(Messager messager, TreeMaker maker, Name.Table names) {
            this.messager = messager;
            this.maker = maker;
            this.names = names;
        }

        @Override
        public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
            super.visitMethodDef(jcMethodDecl);
            boolean isVisited = false;
            if (jcMethodDecl.mods != null && jcMethodDecl.mods.annotations != null) {
                List<JCTree.JCAnnotation> ans = jcMethodDecl.mods.annotations;
                for (JCTree.JCAnnotation annotation : ans) {
                    if (annotation.annotationType.toString().equals("JavascriptInterface")) {
                        isVisited = true;
                        break;
                    }
                }
            }
            if (isVisited) {
                JCTree.JCExpression invokeMethod = maker.Apply(List.nil(),
                        maker.Select(maker.Ident(names.fromString("mWebActivity")),
                                names.fromString(
                                        "getCurrentUrl")), List.nil());
                JCTree.JCVariableDecl statement = makeVarDef(maker.Modifiers(0), "surl",
                        memberAccess("java.lang.String"), invokeMethod);
                JCTree.JCExpression invokeMethod1 = maker.Apply(List.of(memberAccess("java.lang" +
                                ".String"), memberAccess("java.lang.String")),
                        memberAccess("com.dxdxmm.app.component.web.WebBridgeHelperKt" +
                                ".isHostVerify"), List.of(maker.Ident(names.fromString("surl")),
                                maker.Literal(jcMethodDecl.name.toString())));
                JCTree.JCVariableDecl statement1 = makeVarDef(maker.Modifiers(0), "verify",
                        maker.TypeIdent(TypeTag.BOOLEAN), invokeMethod1);
                JCTree.JCIf statement2 = maker.If(maker.Ident(names.fromString("verify")),
                        jcMethodDecl.body, maker.Skip());
                JCTree.JCReturn statement3 = maker.Return(maker.Literal(false));
                JCTree.JCReturn statement4 = maker.Return(maker.Literal(""));

                ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
                if (!jcMethodDecl.name.toString().equals("<init>")) {
                    statements.append(statement);
                    statements.append(statement1);
                    statements.append(statement2);
                    if (jcMethodDecl.restype != null) {
                        if (jcMethodDecl.restype.toString().equals("String")) {
                            statements.append(statement4);
                        } else if (jcMethodDecl.restype.toString().equals("boolean")) {
                            statements.append(statement3);
                        }
                    }
                }
                JCTree.JCBlock body = maker.Block(0, statements.toList());
                result = maker.MethodDef(
                        jcMethodDecl.getModifiers(),
                        names.fromString(jcMethodDecl.name.toString()),
                        jcMethodDecl.restype,
                        jcMethodDecl.typarams,
                        jcMethodDecl.params,
                        jcMethodDecl.thrown,
                        body,
                        jcMethodDecl.defaultValue
                );
                note(result.toString());
            }
        }

        private JCTree.JCVariableDecl makeVarDef(JCTree.JCModifiers modifiers, String name,
                                                 JCTree.JCExpression vartype,
                                                 JCTree.JCExpression init) {
            return maker.VarDef(
                    modifiers,
                    names.fromString(name),
                    vartype,
                    init
            );
        }

        private JCTree.JCExpression memberAccess(String components) {
            String[] componentArray = components.split("\\.");
            JCTree.JCExpression expr = maker.Ident(names.fromString(componentArray[0]));
            for (int i = 1; i < componentArray.length; i++) {
                expr = maker.Select(expr, names.fromString(componentArray[i]));
            }
            return expr;
        }
    }

    private void note(String msg) {
        messager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(WebModule.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

init:初始化。可以得到 ProcessingEnviroment,ProcessingEnviroment 提供很多有用的工具类 Elements, Types 和 Filer getSupportedAnnotationTypes:指定这个注解处理器是注册给哪个注解的,这里说明是注解 BindView getSupportedSourceVersion:指定使用的 Java 版本,通常这里返回 SourceVersion.latestSupported() process:可以在这里写扫描、评估和处理注解的代码,生成 Java 文件

Messager : 日志打印类,有利于分析 Trees : 抽象语法树 TreeMaker : 抽象语法树操作类 Name.Table : 命名表,可根据名称找到对应的方法,变量,类 TreeTranslator : 抽象语法树转换器,可修改方法,变量,类

其他类不熟悉的自行查询,这里就不一一介绍了。

完成了 Processor 的部分,基本就大功告成了。

apt-library 工具类 可选

在 App Module 的 build.gradle 中添加依赖

dependencies {
    implementation project(':apt-annotation')
}

创建注解工具类 BindViewTools

public class BindViewTools {

    public static void bind(Activity activity) {

        Class clazz = activity.getClass();
        try {
            Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method method = bindViewClass.getMethod("bind", activity.getClass());
            method.invoke(bindViewClass.newInstance(), activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
```java
通过javapoet生成代码

添加依赖
```java
dependencies {
    implementation 'com.squareup:javapoet:1.10.0'
}

新建 ClassCreatorProxy

public class ClassCreatorProxy {
    //省略部分代码...

    /**
     * 创建Java代码
     * @return
     */
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods2())
                .build();
        return bindingClass;

    }

    /**
     * 加入Method
     */
    private MethodSpec generateMethods2() {
        ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(host, "host");

        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
        }
        return methodBuilder.build();
    }


    public String getPackageName() {
        return mPackageName;
    }
}

使用自定义注解

使用工具类时:

public class TestActivity extends AppCompatActivity {

    @BindView(R.id.test)
    TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
        BindViewTools.bind(this);
    }

}

不使用工具类,只修改某个方法时:

@BindView()
public class TestActivity extends AppCompatActivity {

    TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
    }

    @JavascriptInterface
    public void test(){

    }
}

业务咨询:https://soloist.pages.dev

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号