annotationProcessor 自动生成代码(上)

作者: oceanLong | 来源:发表于2018-11-18 17:28 被阅读0次

    概要

    有时候,我们需要开发大量重复的代码。每段代码,只有少数成员变量命名不同。这样的场景在开发接口层时,感觉尤为明显。
    接口类可能只是实现类的抽象形式。但每个实现方法,我们都要写一遍接口。且每个接口方法的命名,可能和实现方法完全一致。
    那么,能否有一种方案,让我们用代码自行生成接口呢?这个方案之前是apt,如今是 annotationProcessor

    快速开始

    annotationProcessor的使用大概分为两部分:annotationannotation-compiler。总体原理是,我们定义annotation,然后在合适的地方使用annotation。当编译器编译到我们使用annotation的地方时,变会执行annotation-compiler生成相应的代码。通过annotation的定义位置和相关参数,我们可以生成不同的代码。

    annotation

    首先我们新建Java-Library,并定义注解类:


    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.TYPE)
    public @interface DoctorInterface {
    }
    

    此时我们只是定义了一个注解DoctorInterface,它暂时还不具有任何实际的意义。

    annotation-compiler

    我们再新建一个Java-Library。


    首先,值得注意的是它的build.gradle,我们需要一些依赖:

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.google.auto.service:auto-service:1.0-rc4' //自动注册注解处理器
        implementation 'com.squareup:javapoet:1.8.0' //javapoet代码生成框架
        implementation project(':router-annotation') //依赖注解模块
    }
    
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    
    

    接着,我们就来尝试实现前面定义的注解DoctorInterface的意义。

    @AutoService(Processor.class)  //自动注册
    @SupportedSourceVersion(SourceVersion.RELEASE_7) //指定java版本
    public class InterfaceProcessor extends AbstractProcessor {
    
    
        private Filer filer;
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            filer = processingEnv.getFiler();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(DoctorInterface.class.getCanonicalName());
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                    .build();
            // HelloWorld class
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(main)
                    .build();
            try {
                // build com.example.HelloWorld.java
                JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // write to file
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }
    
    
    }
    
    

    对于它的具体实现,我们先不避深究。我们首先应该注意到:

        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(DoctorInterface.class.getCanonicalName());
        }
    

    它指定了,这个实现对应的注解类。

    然后,我们可以注意到自动生成的类,其实现在process方法中:

        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                    .build();
            // HelloWorld class
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(main)
                    .build();
            try {
                // build com.example.HelloWorld.java
                JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // write to file
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return true;
        }
    

    实际上,它生成的代码是:

    //  This codes are generated automatically. Do not modify!
    package com.example;
    
    import java.lang.String;
    import java.lang.System;
    
    public final class HelloWorld {
      public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!");
      }
    }
    
    

    使用

    值得注意的是,并不是我们代码写完,开始编译,HelloWorld类就能自行生成。如前面所说,我们对这个Processor指定了注解,只有编译时发现注解,才会生成这个类。

    我们在自己的module的build.gradle中加入:

    dependencies {
        ...
        implementation project(':router-annotation')
        annotationProcessor project(':router-compiler')
    }
    
    

    然后某个Java类上,加入注解:

    import com.ocean.doctor.router_annotation.DoctorInterface;
    
    @DoctorInterface
    public class InterfaceBuilder {
    }
    
    

    这样在编译过程中,我们就可以在对应module的build目录中,找到我们生成的HelloWorld类。

    总结

    以上就是通过Javapoet和annotation自动生成Java代码的一个基本模式。生成代码的具体细节,本文没有深究。关于生成代码的过程中,我们如何加入自己的想法,增加代码的可扩展性,将在下篇讲解。

    如有问题,欢迎指正。

    相关文章

      网友评论

        本文标题:annotationProcessor 自动生成代码(上)

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