精华内容
下载资源
问答
  • 自定义annotationProcessor

    2020-11-20 11:22:37
    使用annotationProcessor插件在编译期创建Java文件 使用在idea中 可以尝试下
  • 什么是 Annotation Processor 构建问题 写过自定义注解处理器的老司机们乍一看这个问题觉得挺简单,是的,因为网上基本通篇都在教你怎么打日志,但是你有没有想过如果连日志都打印不出来的时候你怎么定位呢?譬如...
    什么是 Annotation Processor 构建问题

    写过自定义注解处理器的老司机们乍一看这个问题觉得挺简单,是的,因为网上基本通篇都在教你怎么打日志,但是你有没有想过如果连日志都打印不出来的时候你怎么定位呢?譬如如下代码:

    // 确认 META-INF/services/javax.annotation.processing.Processor 没问题
    // 确认构建脚本没问题,确认注解 Bridge 有被使用且有参与构建
    @AutoService(Processor.class)
    public class TestAnnotationProcessor extends AbstractProcessor {
        public TestAnnotationProcessor() {
            System.out.println("TestAnnotationProcessor constrator");
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            System.out.println("TestAnnotationProcessor init");
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            System.out.println("TestAnnotationProcessor getSupportedAnnotationTypes");
            Set<String> supported = new HashSet<String>();
            supported.add(Bridge.class.getCanonicalName());
            return supported;
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            System.out.println("TestAnnotationProcessor process");
            return true;
        }
    }
    

    运行构建后compileReleaseJavaWithJavac过程中没有先吐我 Annotation Processor 的任意一行日志,直接报错找不到我注解处理器产物类引用(即直接进行了 compile class 环节)。

    你懵逼吗?反正我懵逼了!打印日志不好使了,哈哈,环境确认没问题,什么鬼,直接越过 Annotation Processor 进行 compile 了。

    这时候就需要你稍微深入定位分析(撸javac源码的巨佬请自行飘过),前提就是你需要熟悉下 Annotation Processor 基本原理,然后我们通过一些额外的javac详细日志进行举例分析。

    Annotation Processor 机制

    注解和注解处理器是 JDK5 引入的机制,主要用来为类、方法、字段和参数等 Java 结构提供额外的信息。譬如常见的@Override就是仅仅对 Java 编译器生效的一个注解。Java 允许我们自定义注解,自定义的注解处理器就是用来处理这些自定义注解的(废话),注解处理器触发时机是由javac来处理的,所以整个javac过程的简要步骤如下图:

    在这里插入图片描述

    可以看到,javac编译概要图主要分为如下几步:

    1. 把源文件解析为抽象语法树。
    2. 调用已注册的注解处理器。
    3. 如果注解处理器处理过程中生成了新的源文件,编译器重复第 1、2 步,当注解处理器不再生成新的源文件则进入最后一轮。
    4. 进入真正的 compile 字节码环节生成字节码。

    如上就是注解处理器的核心机制,有了这个核心机制的认识我们就继续往下探索。

    构建工具下 Annotation Processor 的本质

    我们日常开发中(无论是 Java 后端还是 Android 移动端)总是多多少少会用到 JDK 提供的annotation processor能力,无论是什么构建工具(Gradle 或者 Maven 等)本质都是通过javac -processorpath命令参数显式指定哪些 Processer,或者显式声明META-INF/services/javax.annotation.processing.Processor来被javac发现并调用的(参见 google 的 AutoService 框架)。

    正常情况下我们开发中使用及构建 Annotation Processor 技术都是上面几步走的方案,而且大多数照着网络上抄的都能正常工作,每次只用自己处理 process 就挺香的,因为只要按照规则声明放置,其他的 javac都能自己完美调用。

    增强 javac 过程打印暴露问题

    要解决一开始说的 Annotation Processor 中自己加的日志都不打印场景问题,我们需要获取一些额外的信息辅助定位。由于直接使用命令行javac的方式是最原始的操作,我们构建一般采用 Gradle,而 Gradle 的本质还是调用javac,所以下面我们以 Gradle 为例来分析如何定位 Annotation Processor 问题。

    下面简单粗暴点就是直接在根目录的build.gradle中给所有模块添加参数:

    //【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】
    
    // 参数可选,重点是 -verbose -XprintRounds -XprintProcessorInfo
    allprojects {
        gradle.projectsEvaluated {
            tasks.withType(JavaCompile) {
                options.compilerArgs << "-Xlint" << "-verbose" << "-XprintRounds" << "-XprintProcessorInfo" << "-Xmaxerrs" << "100000"
            }
        }
    }
    

    你也可以仅仅在自己有注解处理器的模块中添加,与上面一样,只要加给JavaCompile的参数就行。这里的参数其实就是我们平时命令行javac是否的参数,不懂的可以去命令行执行下javac -help观摩下含义吧,如下(JDK8,不同版本 JDK 略有差异):

    yan@yanDeMackbookPro:~$ javac -help
    用法: javac <options> <source files>
    其中, 可能的选项包括:
    -g                         生成所有调试信息
    ......
    -verbose                   输出有关编译器正在执行的操作的消息
    ......
    -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
    -processorpath <路径>        指定查找注释处理程序的位置
    ......
    

    至于脚本中其他几个在javac -help中没有的参数可以看下官方文档https://docs.oracle.com/en/java/javase/11/tools/javac.html ,里面详细解释了参数含义。

    添加上面参数后一定要将你的构建日志追加到一个磁盘文件中,因为日志会变得非常庞大,同时也变得很容易定位问题。

    通过构建日志分析定位问题

    执行你的构建任务,完毕后分析定位主要分为如下几个步骤,每一步都是一种场景的定位,循序渐进定位分析即可。

    1. 在你的日志中搜索你的 Processor 类名,譬如TestAnnotationProcessor.class,看到的日志会是如下。
    // 如果你的注解处理器在项目中是源码形式的日志
    [loading RegularFileObject[/home/user/yan/test/target/classes/cn/yan/test/TestAnnotationProcessor.class]]
    
    // 如果你的注解处理器在项目中是依赖 jar 形式的日志
    [loading ZipFileIndexFileObject[....../test.jar(cn/yan/test/TestAnnotationProcessor.class)]]
    

    分析: 如果你的日志中搜不到上面信息,说明你的注解处理器没有被添加到javac的 classpath 中。一般问题就是你的META-INF/services/javax.annotation.processing.Processor声明有问题,javac无法找到你的注解处理器。有些同学可能是通过 google 的 AutoService 来生成META-INF/services/javax.annotation.processing.Processor的,这种情况下也要自己检查是否 OK(譬如之前安卓中 AGP 有一段时间的中间过渡版本就修改了 classpath,需要手动将 compile 改成 annotationProcessor 才行)。

    1. 在你的日志中搜索Round关键字,建议直接搜Round 1:这样的格式容易点,看到的日志会是如下。
    Round 1:
            input files: {cn.yan.test.Application, ......, cn.yan.test.UseMarkedAnnotation}
                    annotations: [java.lang.Override, cn.yan.annotation.Bridge]
                    last round: false
    

    上面日志中的input files:部分是扫到的你的源码,annotations:部分就是扫到你代码中使用了哪些注解,如果你注解处理器声明了要处理这种注解(譬如@cn.yan.annotation.Bridge),则日志如上才是正常的。

    分析: 如果你日志中没搜到上面的Round,则说明javac没有触发调用任何注解处理器(无论是你写的还是依赖三方框架的),最大的可疑点就是检查下自己有没有禁用javac注解处理器,也就是确认javac执行时没有-proc:none参数。如果你的日志中有Round,但是input files:annotations:没有你的注解类和使用类,则说明你没有在代码中使用你注解处理器要处理的注解。

    1. 在你的日志中搜索Loaded cn.yan.test.TestAnnotationProcessor关键字,看到的日志会是如下。
    [Loaded cn.yan.test.TestAnnotationProcessor from file:/home/user/yan/test/target/classes/cn/yan/test/TestAnnotationProcessor.class]
    

    分析: 如果你看不到上面这行日志,说明你的注解处理器类自己没有被加载成功,为什么没有我也不知道怎么分析了,但是至少说明没加载成功,你可能需要仔细核对哪里不规范或者不合法导致的了。

    1. 上面都排查完了,如果还是找不到问题原因,不妨换个思路,去仔细检查下你参与构建的普通 java 文件,是否存在语法错误或者什么问题(譬如常量没声明等);如果有,解决完了再试试,别问我为什么,我也没有深入研究javac这块源码,只是我遇到过,且也没有异常堆栈信息,最终发现是合并解决冲突后代码少了一个变量声明,就是单纯的越过了 Annotation Processor 过程直接进行 compile to class 流程了)。
    这个技能有什么鸟用?

    不瞒你说,我也算是老司机了,好些年前 Annotation Processor 就玩的很 6 了,但是最近项目升级构建和 Java8 及 androidX 支持后 merge 了下代码,然后项目中的注解处理器、dataBinding 全部都不工作了,更可气的是,这个不工作是真的很吝啬,什么错误堆栈都没有,大致如下奇葩构建日志:

    //【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':test:compileReleaseJavaWithJavac'.
    // 本来这里该先吐我注解处理器内部的日志,然后才继续 javac 编译,实际什么都没吐
    > Compilation failed; see the compiler error output for details.
    * Exception is:
    org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':moffice:compileReleaseJavaWithJavac'.
    	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:200)
    	......
    Caused by: org.gradle.api.internal.tasks.compile.CompilationFailedException: Compilation failed; see the compiler error output for details.
    	at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:57)
    

    Gradle 构建命令已经添加了各种详细参数供查看堆栈和详细日志,但奇妙的事情就是他走到compileReleaseJavaWithJavac就直接出错了,前后没有任何错误提示(有的只是一坨 Gradle 自己的 task 调用链)。我特么大意了,我就同步了下代码,编不过就编不过啊,你倒是提示下问题啊!啥也不提示直接干到 compile class 环节了,跳过了 Annotation Processor 流程,这就很恼火了。好在按照上面方式定位修复了,哈哈。

    左侧码走一波啊!

    展开全文
  • AnnotationProcessor_Demo

    2017-09-18 16:26:10
    使用 AnnotationProcessor实现的编译生成代码使用,使用 AnnotationProcessor实现的编译生成代码,使用 AnnotationProcessor实现的编译生成代码
  • Android AnnotationProcessor

    千次阅读 2019-12-26 20:59:42
    Android AnnotationProcessor一.项目结构二.定义注解三.实现注解处理器(一)依赖(二)注解处理器(三)处理注解四.使用注解处理器(一)依赖(二)使用注解(三)生成的代码五.注意事项 注解处理器通常可以用在模块间解藕、...

    注解处理器通常可以用在模块间解藕、自动生成代码等地方,比如router路由或者butterknife。效果就是我们在某些地方标注某些注解,在编译时,注解处理器会扫描这些注解的地方,然后生成一些代码,这样做可以实现全局的一些自动化功能,并不用开发者感知,非常方便,有点类似gradle插件。

    这里以一个demo演示下通常需要怎么实现注解处理器的功能。
    demo的功能很简单:通过注解的方式,自动生成接口和实现类的注册关系,在主项目里可以直接调用接口方法,不用注册。

    一.项目结构

    首先我们创建一个Android项目,添加两个module。
    在这里插入图片描述

    • app:项目主module
    • lib_annotations:定义注解的java-module,通常会把一些功能注解,定义为一个aar
    • lib_compiler:定义注解处理器的java-module,不会被打入apk,只在编译时使用

    二.定义注解

    首先在lib_annotations里定义我们的注解。

    @Retention(RetentionPolicy.SOURCE)
    @Target(value = ElementType.TYPE)
    public @interface ByteService {
        Class<?> clazz() default Object.class;
    }
    

    这里定义了一个ByteService的注解类,有一个clazz参数用于指定对应的服务接口,默认为Object.class。
    @Retention(RetentionPolicy.SOURCE)指定注解只保留在源文件中,不会被保留到class字节码文件中:因为在编译前期通过扫描源文件就使用完了注解。
    @Target(value = ElementType.TYPE)指定该注解只能使用在类上:因为我们的功能是注册接口和实现类的关系。

    三.实现注解处理器

    接着在lib_compiler里定义你注解处理器。

    (一)依赖

    dependencies {
        implementation 'com.google.auto.service:auto-service:1.0-rc4'
        implementation 'com.squareup:javapoet:1.11.1'
        implementation project(':lib_annotations')
    }
    

    首先引入相关依赖:

    • auto-service:用于自动识别注解处理器的功能
    • javapoet:用于生成java代码的工具sdk
    • lib_annotations:要使用上面定义的注解,引入本地库

    (二)注解处理器

    然后定义处理类,处理类要继承自AbstractProcessor类。

    @AutoService(Processor.class)
    public class ByteProcessor extends AbstractProcessor {}
    

    使用@AutoService(Processor.class)注解,会在编译时,由auto-service库在jar包的/java/main/META-INF/services/下生成一个javax.annotation.processing.Processor文件,内容就是注解处理器类的类名,也是后续编译器识别的标示。
    在这里插入图片描述
    在这里插入图片描述

    (三)处理注解

    我们来看看Processor的具体实现。

    private Filer filer;
    private Messager messager;
    private Map<String, String> mapper = new HashMap<>();
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
    }
    

    首先init()方法可以通过全局的ProcessingEnvironment环境对象,获取一些功能对象。

    • Filer:用于生成新的java文件的对象
    • Messager:用于输出log的对象
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> res = new HashSet<>();
        res.add(ByteService.class.getCanonicalName());
        return res;
    }
    

    getSupportedAnnotationTypes()方法用于返回该Processor想要接收处理的注解,要返回全路径类名,通常使用getCanonicalName()方法。该方法也可以通过在Processor类上定义SupportedAnnotationTypes注解的方式指定。

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //获取该注解的元素
        Set<? extends Element> sets = roundEnvironment.getElementsAnnotatedWith(ByteService.class);
        if (sets != null && sets.size() > 0) {
            for (Element element : sets) {
                //每一个元素由于只能是类,所以都是TypeElement类型
                if (element instanceof TypeElement) {
                    //获取定义你该注解的元素(这里是类)的全路径名称
                    String implName = TypeName.get(element.asType()).toString();
                    //对应的接口全路径类名
                    String interName;
                    try {
                        //通过注解的clazz对象直接获取  
                        interName = element.getAnnotation(ByteService.class).clazz().getCanonicalName();
                    } catch (MirroredTypeException mte) {
                        //由于调用clazz对象时,可能因为Class对象还没有被加载,所以抛异常
                        //异常中有相关class对象的信息,直接拿到类名即可
                        interName = TypeName.get(mte.getTypeMirror()).toString();
                    }
                    //如果没有定义你clazz(默认为Object),则取该类默认实现的接口
                    if (Object.class.getCanonicalName().equals(interName)) {
                        List<? extends TypeMirror> typeMirrors = ((TypeElement) element).getInterfaces();
                        interName = TypeName.get(typeMirrors.get(0)).toString();
                    }
                    //放入map中后续生成代码
                    mapper.put(interName, implName);
                    //messager输出log
                    messager.printMessage(Diagnostic.Kind.NOTE, "Interface: " + interName + " Impl: " + implName);
                }
            }
            //生成代码
            generate();
        }
        return true;
    }
    

    注释已经解释的很清楚,大概分几步:

    1. 获取指定注解的所有元素
    2. 拿到元素的类名
    3. 拿到元素标注的接口的类名
    4. 存入map,输出log
    5. 开始生成代码
    private void generate() {
        //private constructor
        MethodSpec cons = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
        //static map
        ParameterizedTypeName mapType = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Class.class), ClassName.get(Object.class));
        FieldSpec map = FieldSpec.builder(mapType, "services", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).initializer("new java.util.HashMap<>()").build();
        //static init
        FieldSpec init = FieldSpec.builder(Boolean.class, "isInit", Modifier.PRIVATE, Modifier.STATIC).initializer("false").build();
        //static getService
        MethodSpec.Builder getServiceBuilder = MethodSpec.methodBuilder("getService").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
        TypeVariableName t = TypeVariableName.get("T");
        TypeVariableName b = TypeVariableName.get("B").withBounds(t);
        getServiceBuilder.addTypeVariable(t).addTypeVariable(b);
        getServiceBuilder.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), t), "clazz");
        getServiceBuilder.returns(b);
        //statement
        getServiceBuilder.beginControlFlow("if(!isInit)");
        generateInitStatement(getServiceBuilder).addStatement("isInit=true").endControlFlow();
        getServiceBuilder.addStatement("return (B) services.get(clazz)");
        //class
        TypeSpec typeSpec = TypeSpec.classBuilder("ServiceManager")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addField(init)
                .addField(map)
                .addMethod(cons)
                .addMethod(getServiceBuilder.build())
                .build();
        //file
        JavaFile javaFile = JavaFile.builder("com.util.service", typeSpec).build();
        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
        }
    }
    

    生成代码主要是使用javapoet提供的功能:

    1. MethodSpec生成方法和构造器
    2. FieldSpec生成字段
    3. TypeVariableName生成泛型定义
    4. ParameterizedTypeName生成类型,可以包含泛型
    5. TypeSpec生成类
    6. JavaFile生成文件
    7. Filer提供写入文件功能

    这里注册代码是通过generateInitStatement()方法,把map里的所有对象直接插入到代码里实现的。

    private MethodSpec.Builder generateInitStatement(MethodSpec.Builder getServiceBuilder) {
        for (Map.Entry<String, String> entry : mapper.entrySet()) {
            getServiceBuilder.addStatement(String.format("services.put(%s.class,new %s())", entry.getKey(), entry.getValue()));
        }
        return getServiceBuilder;
    }
    

    至此就完成了注解处理器的定义。

    四.使用注解处理器

    完成类注解处理器,主module就可以使用了。

    (一)依赖

    dependencies {
        ...
        annotationProcessor project(':lib_complier')
        implementation project(':lib_annotations')
    }
    

    我们要使用注解,所以要引入lib_annotations库。
    引入注解处理器时,使用annotationProcessor即可,在编译时,会自动识别上面说的META-INF里的类名,找到类进行注解处理器的执行。

    (二)使用注解

    我们定义了两个接口和两个实现类。
    在这里插入图片描述

    @ByteService
    public class Service1Impl implements IService1 {
        @Override
        public void doFun() {
            System.out.println("doFun");
        }
    }
    
    @ByteService(clazz = IService2.class)
    public class Service2Impl implements ITest, IService2 {
    
        @Override
        public void doTest() {
            System.out.println("doTest");
        }
    
        @Override
        public void test() {
            System.out.println("test");
        }
    }
    

    其中Service1Impl由于只有一个接口所以采用默认的注解,Service2Impl有两个接口,指定注解的clazz接口为IService2。
    在我们build一次后,就可以直接调用ServiceManager使用。

    ServiceManager.getService(IService1.class).doFun();
    ServiceManager.getService(IService2.class).doTest();
    

    (三)生成的代码

    而注解处理器生成的代码如下。

    public final class ServiceManager {
      private static Boolean isInit = false;
    
      private static final Map<Class, Object> services = new java.util.HashMap<>();
    
      private ServiceManager() {
      }
    
      public static <T, B extends T> B getService(Class<T> clazz) {
        if(!isInit) {
          services.put(com.example.byteapplication.annotation_process.IService1.class,new com.example.byteapplication.annotation_process.Service1Impl());
          services.put(com.example.byteapplication.annotation_process.IService2.class,new com.example.byteapplication.annotation_process.Service2Impl());
          isInit=true;
        }
        return (B) services.get(clazz);
      }
    }
    

    五.注意事项

    1. javapoet生成类时,需要注意import相关类,或者通过全路径类名使用参考文章
    2. 注解处理器也可以debug调试参考文章
    3. 使用java注解处理器处理不了kotlin相关的代码,可以使用kapt参考文章
    展开全文
  • annotationProcessor 继承 自动创建类,获取注解对象,持续更新中
  • JPA Annotation Processor 基于“Better JPA、Better JAXB 和 Better Annotations Processing with Java SE 6” ================================================== ============================ JPA ORM 注释...
  • MpApt - Kotlin (Native/JS/JVM) Annotation Processor library I wrote an annotation processing libary that can detect annotations in Kotlin Native/JS and Jvm projects, because Kapt is only ...
  • Gradle子项目中的注释处理器 Gradle子项目中的注释处理器示例 参见 。
  • 因此,ButterKnife本身并非基于注解+反射来实现的,而是用Annotation Processor在编译时处理注解的。Annotation Processor什么呢?本篇文章就来认识下Annotation Processor吧。 Annotation Processor...

    上篇文章我们使用注解+反射实现了一个仿ButterKnife功能的示例。考虑到反射是在运行时完成的,多少会影响程序性能。因此,ButterKnife本身并非基于注解+反射来实现的,而是用Annotation Processor在编译时处理注解的。Annotation Processor什么呢?本篇文章就来认识下Annotation Processor吧。

    Annotation Processor使用须知
    1.了解Annotation Processor 
    Annotation Processor是javac的一个工具,它用来在编译时扫描和处理注解。通过Annotation Processor可以获取到注解和被注解对象的相关信息,然后根据注解自动生成Java代码,省去了手动编写,提高了编码效率。使用注解处理器先要了解AbstractProcessor类,这个类是一个抽象类,有四个核心方法,关于AbstractProcessor类后面再做详细解析。

    2.在Android Studio使用Annotation Processor 
    刚接触Annotation Processor的同学可能会遇到找不到AbstractProcessor类的问题,大概率是因为直接在Android项目里边引用了AbstractProcessor,然而由于Android平台是基于OpenJDK的,而OpenJDK中不包含Annotation Processor的相关代码。因此,在使用Annotation Processor时,必须在新建Module时选择Java Library,处理注解相关的代码都需要在Java Library模块下完成。我们需要看一下整个项目的结构

    annotation模块(Java Library) 该模块存放的是我们自定义的注解,是一个Java Library 
    compiler模块 (Java Library) 依赖annotation模块,处理注解并自动生成代码等,同样也是Java Library。 
    app (Android App) 依赖compiler模块,需要使用annotationProcessor依赖compiler模块

    有了以上知识,接下来我们就可以用一个实例来学习Annotation Processor的用法啦。

    Annotation Processor使用实例
    现在,我们来引入一个工厂模式的例子。我们先来定义一个形状的接口Shape,代码如下:

    public interface Shape {
        void draw();
    }

    接下来定义几个形状并实现Shape接口

    public class Rectangle implements Shape {
        @Override
        public void draw() {
            System.out.println("Draw a Rectangle");
        }
    }

    public class Triangle implements Shape {
        @Override
        public void draw() {
            System.out.println("Draw a Triangle");
        }
    }

    public class Circle implements Shape {  
        @Override
        public void draw() {   
            System.out.println("Draw a circle");
        }
    }

    然后我们需要一个工厂类来创建形状,代码如下:

    public class ShapeFactory {
      public Shape create(String id) {
        if (id == null) {
          throw new IllegalArgumentException("id is null!");
        }
        if ("Circle".equals(id)) {
          return new Circle();
        }
        if ("Rectangle".equals(id)) {
          return new Rectangle();
        }
        if ("Triangle".equals(id)) {
          return new Triangle();
        }
        throw new IllegalArgumentException("Unknown id = " + id);
      }
    }

    然而,在程序中,我们随时可能会添加一个新的形状。那么此时就不得不修改工厂类来适配新添加的形状了。我们在上边已经提到过使用Annotation Processor可以帮助我们自动生成代码。那么这个工厂类是不是可以使用Annotation Processor来自动生成呢?这样就不需要在添加新的形状后手动修改ShapeFactory类了。仅仅需要在新添加的形状类上加上一个注解,注解处理器就会在编译时根据注解信息自动生成ShapeFactory类的代码。 
    制定了需求目标,接下来的任务就是来实现ShapeFactory的自动生成了。我们首先在annotation模块下定义一个Factory的注解,Factory注解的Target为ElementType.TYPE表示这个注解只能用在类、接口或者枚举上。Retention指定为RetentionPolicy.CLASS。Factory注解中有两个成员,一个Class类型的type,用来表示注解的类的类型,相同的类型表示属于同一个工厂。一个String类型的id,用来表示注解的类的名称。Factory注解代码如下:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface Factory {

        Class type();

        String id();
    }

    用Factory来注解形状类

    @Factory(id = "Rectangle", type = Shape.class)
    public class Rectangle implements Shape {
        @Override
        public void draw() {
            System.out.println("Draw a Rectangle");
        }
    }
    ... 其他形状类代码类似不再贴出

    声明FactorProcessor注解处理器
    在factory-compiler模块下新建FactoryProcessor类并继承AbstractAnnotation类,代码如下:

    @AutoService(Processor.class)
    public class FactoryProcessor extends AbstractProcessor {
         private Types mTypeUtils;
        private Messager mMessager;
        private Filer mFiler;
        private Elements mElementUtils;
        private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<>();


        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mTypeUtils = processingEnvironment.getTypeUtils();
            mMessager = processingEnvironment.getMessager();
            mFiler = processingEnvironment.getFiler();
            mElementUtils = processingEnvironment.getElementUtils();
        }

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

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

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                //  通过RoundEnvironment获取到所有被@Factory注解的对象
                for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
                    ...

                }


            return true;
        }

        private void error(Element e, String msg, Object... args) {
            messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
        }

        private void error(String msg, Object... args) {
            messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
        }

        private void info(String msg, Object... args) {
            messager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
        }
    }

    可以看到,在这个类上添加了@AutoService注解,它的作用是用来生成META-INF/services/javax.annotation.processing.Processor文件的,也就是我们在使用注解处理器的时候需要手动添加META-INF/services/javax.annotation.processing.Processor,而有了@AutoService后它会自动帮我们生成。AutoService是Google开发的一个库,使用时需要在factory-compiler中添加依赖,如下:

    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    1
    接下来针对AbstractProcessor中的四个方法来做解析 
    init(ProcessingEnvironment processingEnvironment) 
    这个方法用于初始化处理器,方法中有一个ProcessingEnvironment类型的参数,ProcessingEnvironment是一个注解处理工具的集合。如Filer可以用来编写新文件,Messager可以用来打印错误信息,还有Elements是一个可以处理Element的工具类。在这里我们有必要对Element做下说明 
    Element是一个接口,表示一个程序元素,它可以是包、类、方法或者一个变量。Element已知的子接口有:

    PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。 
    ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。 
    TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。 
    VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

    我们可以通过一个类来认识Element元素。

    package com.zhpan.mannotation.factory;  //    PackageElement

    public class Circle {  //  TypeElement

        private int i; //   VariableElement
        private Triangle triangle;  //  VariableElement

        public Circle() {} //    ExecuteableElement


        public void draw(   //  ExecuteableElement
                            String s)   //  VariableElement
        {
            System.out.println(s);
        }

        @Override
        public void draw() {    //  ExecuteableElement
            System.out.println("Draw a circle");
        }
    }

    getSupportedSourceVersion() 
    这个方法用来指定当前正在使用的Java版本,通常指定SourceVersion.latestSupported()。 
    getSupportedAnnotationTypes() 
    这个方法的返回值是一个Set集合,集合中指定支持的注解类型的名称(这里必须时完整的包名+类名,例如com.example.annotation.Factory),在本例中只需要处理@Factory注解,因此Set集合中只添加了@Factory,如若有其他注解,可在此处一并添加。 
    process(Set《? extends TypeElement》 set, RoundEnvironment roundEnvironment) 处理先前 round 产生的类型元素上的注解类型集,并返回这些注解是否由此 Processor 处理。如果返回 true,则这些注解已处理后续 Processor 无需再处理它们;如果返回 false,则这些注解未处理并且可能要求后续 Processor 处理它们。在这个方法中,我们可以校验被注解的对象是否合法,可以编写处理注解的代码,以及自动生成java文件等。这个方法也是AbstractProcessor 中的最重要的一个方法,我们的大部分逻辑都是在这个方法中完成。后边的内容也主要是逐步完善这个方法。

    上述FactoryProcessor 代码中在process方法中通过roundEnv.getElementsAnnotatedWith(Factory.class)方法已经拿到了被注解的元素的集合。正常情况下,这个集合中应该包含的是所有被Factory注解的Shape类的元素,也就是一个TypeElement。但在编写程序代码时可能有不太了解@Factory的用途而误把@Factory用在接口或者抽象类上,这是不被允许的。因此,需要在process方法中判断该元素是否是一个类,如果不是一个类元素,那么就抛出异常,终止编译。代码如下:

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
         //  通过RoundEnvironment获取到所有被@Factory注解的对象
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
            if (annotatedElement.getKind() != ElementKind.CLASS) {
                 throw new ProcessingException(annotatedElement, "Only classes can be annotated with @%s",
                        Factory.class.getSimpleName());
                 }
                 TypeElement typeElement = (TypeElement) annotatedElement;
                 FactoryAnnotatedClass annotatedClass = new FactoryAnnotatedClass(typeElement);
                    ...
            }
            return true;
    }

    另外我们需要声明一个FactoryAnnotatedClass来存放annotatedElement的相关信息。FactoryAnnotatedClass代码如下:

    public class FactoryAnnotatedClass {
        private TypeElement mAnnotatedClassElement;
        private String mQualifiedSuperClassName;
        private String mSimpleTypeName;
        private String mId;

        public FactoryAnnotatedClass(TypeElement classElement) {
            this.mAnnotatedClassElement = classElement;
            Factory annotation = classElement.getAnnotation(Factory.class);
            mId = annotation.id();
            if (mId.length() == 0) {
                throw new IllegalArgumentException(
                        String.format("id() in @%s for class %s is null or empty! that's not allowed",
                                Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
            }

            // Get the full QualifiedTypeName
            try {  // 该类已经被编译
                Class<?> clazz = annotation.type();
                mQualifiedSuperClassName = clazz.getCanonicalName();
                mSimpleTypeName = clazz.getSimpleName();
            } catch (MirroredTypeException mte) {// 该类未被编译
                DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
                TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                mQualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
                mSimpleTypeName = classTypeElement.getSimpleName().toString();
            }
        }

        ...
    }

    为了生成合乎要求的ShapeFactory类,在生成ShapeFactory代码前需要对被Factory注解的元素进行校验,只有通过校验,符合要求了才可以生成ShapeFactory代码。根据需求,我们列出如下规则:

    1.只有类才能被@Factory注解。因为在ShapeFactory中我们需要实例化Shape对象,虽然@Factory注解声明了Target为ElementType.TYPE,但接口和枚举并不符合我们的要求。 
    2.被@Factory注解的类中需要有public的构造方法,这样才能实例化对象。 
    3.被注解的类必须是type指定的类的子类 
    4.id需要为String类型,并且需要在相同type组中唯一 
    5.具有相同type的注解类会被生成在同一个工厂类中

    根据上面的规则,我们来一步步完成校验,如下代码:


    private void checkValidClass(FactoryAnnotatedClass item) throws ProcessingException {

            // Cast to TypeElement, has more type specific methods
            TypeElement classElement = item.getTypeElement();
            // Check if it's a public class
            if (!classElement.getModifiers().contains(Modifier.PUBLIC)) { 
                throw new ProcessingException(classElement, "The class %s is not public.",
                        classElement.getQualifiedName().toString());
            }

            // Check if it's an abstract class
            if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
                throw new ProcessingException(classElement,
                        "The class %s is abstract. You can't annotate abstract classes with @%",
                        classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
            }

            // Check inheritance: Class must be child class as specified in @Factory.type();
            TypeElement superClassElement = mElementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
            if (superClassElement.getKind() == ElementKind.INTERFACE) {
                // Check interface implemented
                if (!classElement.getInterfaces().contains(superClassElement.asType())) {
                    throw new ProcessingException(classElement,
                            "The class %s annotated with @%s must implement the interface %s",
                            classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                            item.getQualifiedFactoryGroupName());
                }
            } else {
                // Check subclassing
                TypeElement currentClass = classElement;
                while (true) {
                    /**
                     * getSuperclass()
                     * Returns the direct superclass of this type element.
                     * If this type element represents an interface or the class java.lang.Object,
                     * then a NoType with kind NONE is returned.
                     */
                    TypeMirror superClassType = currentClass.getSuperclass();

                    if (superClassType.getKind() == TypeKind.NONE) {
                        // Basis class (java.lang.Object) reached, so exit
                        throw new ProcessingException(classElement,
                                "The class %s annotated with @%s must inherit from %s",
                                classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
                                item.getQualifiedFactoryGroupName());
                    }

                    if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
                        // Required super class found
                        break;
                    }

                    // Moving up in inheritance tree
                    currentClass = (TypeElement) mTypeUtils.asElement(superClassType);
                }
            }

            // Check if an empty public constructor is given
            for (Element enclosed : classElement.getEnclosedElements()) {
                if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
                    ExecutableElement constructorElement = (ExecutableElement) enclosed;
                    if (constructorElement.getParameters().size() == 0 &&
                            constructorElement.getModifiers().contains(Modifier.PUBLIC)) {
                        // Found an empty constructor
                        return;
                    }
                }
            }

            // No empty constructor found
            throw new ProcessingException(classElement,
                    "The class %s must provide an public empty default constructor",
                    classElement.getQualifiedName().toString());
        }

    另外还需声明FactoryGroupedClasses来存放FactoryAnnotatedClass,并且这个类中包含了自动生成ShapeFactory类的代码。FactoryAnnotatedClass和FactoryGroupedClasses是将注解信息封装成对象方便处理。代码如下:

    public class FactoryGroupedClasses {
        /**
         * Will be added to the name of the generated factory class
         */
        private static final String SUFFIX = "Factory";
        private String qualifiedClassName;

        private Map<String, FactoryAnnotatedClass> itemsMap = new LinkedHashMap<>();

        public FactoryGroupedClasses(String qualifiedClassName) {
            this.qualifiedClassName = qualifiedClassName;
        }

        public void add(FactoryAnnotatedClass toInsert) {
            FactoryAnnotatedClass factoryAnnotatedClass = itemsMap.get(toInsert.getId());
            if (factoryAnnotatedClass != null) {
                throw new IdAlreadyUsedException(factoryAnnotatedClass);
            }
            itemsMap.put(toInsert.getId(), toInsert);
        }

      public void generateCode(Elements elementUtils, Filer filer) throws IOException {
            //  Generate java file
            ...
        }
    }

    JavaPoet自动生成代码
    校验完注解信息,如果符合我们制定的规则就可以来生成ShapeFactory的代码了。我们使用square公司的一个开源框架JavaPoet来生成代码。关于这个框架大家可以去github学习如何使用,这里不再详细说明。gradel中添加以下依赖:

    compile 'com.squareup:javapoet:1.11.1'
    1
    JavaPoet构建并自动生成ShapeFactory类的代码如下:

    public void generateCode(Elements elementUtils, Filer filer) throws IOException {
            TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
            String factoryClassName = superClassName.getSimpleName() + SUFFIX;
            String qualifiedFactoryClassName = qualifiedClassName + SUFFIX;
            PackageElement pkg = elementUtils.getPackageOf(superClassName);
            String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();

            MethodSpec.Builder method = MethodSpec.methodBuilder("create")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(String.class, "id")
                    .returns(TypeName.get(superClassName.asType()));

            // check if id is null
            method.beginControlFlow("if (id == null)")
                    .addStatement("throw new IllegalArgumentException($S)", "id is null!")
                    .endControlFlow();

            // Generate items map

            for (FactoryAnnotatedClass item : itemsMap.values()) {
                method.beginControlFlow("if ($S.equals(id))", item.getId())
                        .addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
                        .endControlFlow();
            }

            method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");

            TypeSpec typeSpec = TypeSpec
                    .classBuilder(factoryClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(method.build())
                    .build();

            // Write file
            JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
        }

    最后,完善process方法中的代码,如下:

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
                ...

                FactoryGroupedClasses factoryClass = factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
                if (factoryClass == null) {
                     String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
                     factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
                     factoryClasses.put(qualifiedGroupName, factoryClass);
                }
                factoryClass.add(annotatedClass);
            }

        // Generate code
         for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
                    factoryClass.generateCode(mElementUtils, mFiler);
          }
         factoryClasses.clear();
        }catch (ProcessingException e) {
                error(e.getElement(), e.getMessage());
        } catch (IOException e) {
                e.printStackTrace();
        }

        return true;
    }

    到此为止,Annotation Processor已经可以帮我们自动来生成需要的Java文件啦。接下来Build一下项目,切换到project模式下,在app–>build–>generated–>source–>apt–>debug–>(package)–>factory下面就可以看到ShapeFactory类,如下图: 

    这个类并非是我们自己编写的,而是通过上面一系列骚操作自动生成来的。现在可以再添加一个形状类继承Shape并附加@Factory注解,再次编译后都自动会生成到ShapeFactory中!到这里本篇文章就告一段落了,由于本篇文章结构比较复杂且代码也比较多,项目的源码已经放在文章末尾,可作参考。

    源码下载

    参考资料 
    http://www.cjsdn.net/Doc/JDK60/javax/annotation/processing/AbstractProcessor.html 
    https://www.race604.com/annotation-processing/
    --------------------- 
    作者:Smilyyy 
    来源:CSDN 
    原文:https://blog.csdn.net/qq_20521573/article/details/82321755 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • IDEA+Gradle使用Annotation Processor

    千次阅读 2019-05-09 16:40:03
    IDEA+Gradle使用Annotation Processor一、前言二、问题三、解决方法四、需要注意的细节 一、前言 在使用过一些知名框架如butterknife之后,发现原来还可以通过编译时注解来生成java类,于是准备大干一场却没想到被...

    一、前言

    在使用过一些知名框架如butterknife之后,发现原来还可以通过编译时注解来生成java类,于是准备大干一场却没想到被idea和gradle难住了!!!一连查了两天,终于被我解决了。

    二、问题

    在idea+gradle的环境中使用annotation processor生成代码,但是代码生成在了build文件夹下的classes里,且程序无法引用生成的类,若强行使用则报错找不到类

    三、解决方法

    • 第一步 配置idea
      file -> settting -> Build,Execution,Deployment -> compiler -> annotation processor 进入此界面,然后按照图示操作
      idea setting- 第二步 配置gradle,使编译时将文件生成到指定目录
    compileJava {
    		//配置编译时生成代码的目录
            options.compilerArgs << "-s"
            options.compilerArgs << "$projectDir/src/main/generated/java"
    		//确保文件夹存在
            doFirst {
                file(new File(projectDir, "/src/main/generated/java")).mkdirs()
            }
        }
    	//在clean时删除编译生成的代码
        clean.doLast {
            // clean-up directory when necessary
            file(new File(projectDir, "/src/main/generated")).deleteDir()
        }
    
    • 第三步 配置文件夹类型
      在执行gradle的build任务后,会在src/main下生成指定目录以及代码,但是在我们的源文件中依然没有提示,那么我们需要指定其文件夹类型,如下图
      file dirsign
      在此之后就可以愉快的生成代码了

    四、需要注意的细节

    1. 在编写自定义Processor可能会出现处理器不起作用的情况
      答:其很有可能是你将Processor.class写成了Process.class
    	//正确的写法
    	@AutoService(Processor.class)	
    	//错误的写法
    	@AutoService(Process.class)
    
    1. 即使是按照上面的步骤配置,但仍然无法引用生成的代码(删除线即当时认知错误,在第4点会讲原因)
      答:可能是由于你生成代码的文件夹与你的源文件不在一个module中,由于idea中使用gradle创建一个module,他会识别为三个module ,如下图
      module我只是创建了一个Test module,但是他识别为三个。其中的test_main module就是我们日常开发的module,其路径是**/Test/src/main**,
      如果你的生成代码不在这个路径内,那么他不属于这个module,所以无法引用

    2. annotation processor 生成类时报异常:错误: 类重复: com.cxyz.test.Test
      答:其实annotation processor只能生成额外的类,而不能在原先类的基础上做改动

    3. 其实第2点中idea识别为三个module的原因是你在创建项目的时候勾选了如下图的选项,取消勾选就行了:
      error

    五、参考文章

    1. http://www.it1352.com/830868.html
    2. https://stackoverflow.com/questions/34188732/get-intellij-to-recognize-classes-generated-by-annotationprocessor
    展开全文
  • annotationProcessor.rar

    2018-04-19 16:49:39
    自定义注解编译器,根据注解自动生成代码,减少代码之间的耦合
  • 注解Annotation Processor学习

    千次阅读 2018-11-23 16:53:03
    Annotation processors must be explicitly declared now 在app的build中 android { ... defaultConfig { ... //添加如下配置就OK了 javaCompileOptions { annotationProcessorOpt...
  • Gradle之apt, annotationProcessor和kapt

    千次阅读 2020-03-16 21:52:42
    annotationProcessor annotationProcessor在Android Gradle 2.2之后由Google引入的,是Gradle中内置的APT工具,同时支持 javac 和 jack 编译方式。使用方式(即Java语言下的使用方式)如下: dependencies { ...
  • Annotation Proccessor注解处理器在android 开源项目中应用广泛,比如大神JakeWharton的ButterKnife。它通过在编译期扫描注解,生成模板类,运行时通过反射调用生成的模板类,以解耦项目,减少模板代码。在项目进行...
  • idea中出部分文件提示“spring boot Configuration Annotation Processor not configured"。 原因 提示说明没有配置springboot的自动配置注解处理器,及spring-boot-configuration-processor。 在使用到...
  • annotationProcessor project(':processorlib') build之前会HelloWorld会报错,因为没有生成java文件,在build之后就可以了,在build/generated/source/apt/debug下面生成了HelloWorld文件     ...
  • annotationProcessor的解释

    千次阅读 2019-04-18 15:13:25
    在日常的Android开发过程,我们不可避免地会在项目中引入一些第三方库,以引入Butterknife为例: ...annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0' ...1.什么是annotationProcessor ann...
  • 原始网页直通车 Transform 对字节码进行操作,作用在 java 代码编译生成 class 文件之后, class 打包...Annotation 在对 java 代码进行编译时,根据元注解获取到的 java 代码信息,生成相应的辅助类。 源码级别的操
  • 进行Spring Boot配置文件部署时,发出警告Spring Boot Configuration Annotation Processor not configured,但是不影响运行。 问题解决方案: 在pom.xml文件中引入依赖 <dependency> <groupId>...
  • 问题:使用 butterknife 时,有两个依赖配置: ...annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0' 将上述配置写在BaseLibMoudle中,在此moudle中的BaseFragment中统一配置...
  • annotationProcessor’ dependencies won’t be recognized as kapt annotation processors. Please change the configuration name to ‘kapt’ for these artifacts: ...
  • 你可能经常在build.gradle文件中看到,这样的字眼,annotationProcessor、android-apt、Provided,它们到底有什么作用?下面就一起来看看吧 1、什么是APT? 随着一些如ButterKnife,dagger等的开源注解框架的...
  • Android Studio 3.5 使用@AutoService(Processor.class)注册annotation processor Android Plugin for Gradle: <=3.3.2 && Gradle Version 4.10.2 ...自己编写的annotation processor(Abstra
  • // 注解处理器(我的项目配置了lombok和springboot注解处理器) annotationProcessor( 'org.projectlombok:lombok:1.18.10', 'org.springframework.boot:spring-boot-configuration-processor' ) 在这里build: ...
  • SpringBoot在读取配置文件中的属性的时候,出现Spring Boot Configuration Annotation Processor NotConfigured错误 错误原因是没有使用到Spring Boot的注解生成器来生成元数据 文档查看 意思是需要在POM上加入 &...
  • 随着一些使用注解生成器(annotationProcessor)的框架的流行,例如ButterKnife、dagger2、EventBus 3.0。我需要了解注解生成器的相关知识。 APT APT(Annotation Processing Tool)是一种处理注解的工具,它对源...
  • 1、springboot在通过@ConfigurationProperties使用application.yml配置的属性时提示:Spring Boot Configuration Annotation Processor not configured,如下图所示: 2、此时只需要添加如下依赖即可: <...
  • 上节中AutoService利用注解处理生成的Service文件,这次我们主要进行代码生成,分析注解处理过程中代码生成的过程以及Gradle和Maven应如何配置Annotation Processor的支持。 @Retention(RetentionPolicy.SOURCE) @...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,506
精华内容 12,202
关键字:

annotationprocessor