共计 5980 个字符,预计需要花费 15 分钟才能阅读完成。
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 主要步骤
可以看到,javac 编译概要图主要分为如下几步:
把源文件解析为抽象语法树。
调用已注册的注解处理器。
如果注解处理器处理过程中生成了新的源文件,编译器重复第 1、2 步,当注解处理器不再生成新的源文件则进入最后一轮。
进入真正的 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 中给所有模块添加参数:
// 参数可选,重点是 -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 才行)。
2. 在你的日志中搜索 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: 没有你的注解类和使用类,则说明你没有在代码中使用你注解处理器要处理的注解。
3. 在你的日志中搜索 Loaded cn.yan.test.TestAnnotationProcessor 关键字,看到的日志会是如下。
[Loaded cn.yan.test.TestAnnotationProcessor from file:/home/user/yan/test/target/classes/cn/yan/test/TestAnnotationProcessor.class]
分析: 如果你看不到上面这行日志,说明你的注解处理器类自己没有被加载成功,为什么没有我也不知道怎么分析了,但是至少说明没加载成功,你可能需要仔细核对哪里不规范或者不合法导致的了。
上面都排查完了,如果还是找不到问题原因,不妨换个思路,去仔细检查下你参与构建的普通 java 文件,是否存在语法错误或者什么问题(譬如常量没声明等); 如果有,解决完了再试试,别问我为什么,我也没有深入研究 javac 这块源码,只是我遇到过,且也没有异常堆栈信息,最终发现是合并解决冲突后代码少了一个变量声明,就是单纯的越过了 Annotation Processor 过程直接进行 compile to class 流程了)。
这个技能有什么鸟用?
不瞒你说,我也算是老司机了,好些年前 Annotation Processor 就玩的很 6 了,但是最近项目升级构建和 Java8 及 androidX 支持后 merge 了下代码,然后项目中的注解处理器、dataBinding 全部都不工作了,更可气的是,这个不工作是真的很吝啬,什么错误堆栈都没有,大致如下奇葩构建日志:
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 流程,这就很恼火了。好在按照上面方式定位修复了,哈哈。
关于 Annotation Processor 处理器问题如何深度定位问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注丸趣 TV 行业资讯频道了解更多相关知识。