Maven引入依赖后ToolProvider获取的Java编译器不返回编译错误
问题现象
通过ToolProvider.getSystemJavaCompiler()获取的系统Java编译器存在反常行为:
- 在无额外依赖的“纯净”Maven项目中编译存在语法错误的Java文件时,可正常获取预期的编译错误信息
- 当项目添加特定依赖(最初在引入log4j依赖时发现该现象)后,编译器不再返回任何编译错误相关信息
复现方式
- 已搭建复现工程,工程内实现了简单main方法,用于解析无效Java文件的AST,若diagnostics诊断对象中未检测到编译错误则抛出异常,该main方法由单元测试触发执行
- 工程pom.xml中定义了名为
strange的profile,激活后仅会向项目添加一个未被实际使用的metainf-services依赖,激活profile后该依赖会被加入类路径 - 工程内
run.sh脚本先后执行两次mvn test:第一次不激活profile,第二次激活该profile
复现结果
执行脚本后得到如下结果:
- 未激活profile时测试通过,无效语法被正常检测
- 激活profile后测试失败,添加依赖后无效语法未被检测
对应构建日志如下:
## Build without activated profile [INFO] Scanning for projects... [....] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running SimpleExecTest src/main/resources/Example.java:4: error: ';' expected System.out.println("Hello World!") ^ [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.19 s - in SimpleExecTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 6.832 s [INFO] Finished at: 2022-06-24T00:57:46+02:00 [INFO] ------------------------------------------------------------------------ ## Build with activated profile [INFO] Scanning for projects... [....] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running SimpleExecTest [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.203 s <<< FAILURE! - in SimpleExecTest [ERROR] SimpleExecTest.testMain Time elapsed: 0.171 s <<< ERROR! java.lang.Error: Shall not be possible to compile. at org.fuchss.Main.main(Main.java:46) at SimpleExecTest.testMain(SimpleExecTest.java:7) [....] [INFO] [INFO] Results: [INFO] [ERROR] Errors: [ERROR] SimpleExecTest.testMain:7 » Shall not be possible to compile. [INFO] [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 6.323 s [INFO] Finished at: 2022-06-24T00:57:54+02:00 [INFO] ------------------------------------------------------------------------ [....]
补充信息
- 执行
mvn dependency:tree查看依赖树,metainf-services未引入任何额外的编译范围依赖,依赖树输出如下:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ strange --- [INFO] org.fuchss:strange:jar:1.0-SNAPSHOT [INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test [INFO] | +- org.junit.platform:junit-platform-engine:jar:1.8.2:test [INFO] | | +- org.opentest4j:opentest4j:jar:1.2.0:test [INFO] | | \- org.junit.platform:junit-platform-commons:jar:1.8.2:test [INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test [INFO] | \- org.apiguardian:apiguardian-api:jar:1.1.2:test [INFO] \- org.kohsuke.metainf-services:metainf-services:jar:1.9:compile
- 激活/未激活profile两种场景下执行
mvn -X clean verify的完整调试日志已随复现工程提供。
请问该异常行为的产生原因是什么,如何解决?
原因分析
问题根源是JDK内置编译器的默认注解处理器自动发现逻辑:
- 用
ToolProvider.getSystemJavaCompiler()拿到的系统编译器,默认会扫描类路径下所有jar包,只要jar里存在META-INF/services/javax.annotation.processing.Processor这个服务声明文件,就会自动把这个jar当成注解处理器加载执行。 - 测试用的
metainf-services、最早发现问题时用的log4j依赖,jar包内恰好都带了这个注解处理器声明文件。编译器加载到这些处理器后,会优先走注解处理流程。 - 如果调用编译器时没有显式配置编译参数,注解处理流程跑完后编译器会直接判定流程结束,跳过后续的语法检查、语义检查步骤,直接返回编译成功,自然拿不到任何语法错误的诊断信息。
说白了就是类路径上的普通依赖因为带了注解处理器的服务声明,被编译器误当成要执行的注解处理器,直接打断了正常的语法校验流程。
解决方案
调用编译器时显式添加编译参数,关闭默认的注解处理器自动发现即可,示例代码如下:
// 显式指定不执行任何注解处理,关闭自动扫描逻辑 Iterable<String> compileOptions = Arrays.asList("-proc:none"); // 将参数传入创建的编译任务 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, diagnostics, compileOptions, null, compilationUnits );
如果项目本身确实需要用到注解处理器,不要依赖编译器默认扫描类路径的方式加载,单独通过-processorpath参数指定注解处理器所在的路径,把注解处理器路径和普通业务依赖的类路径分开,避免编译器误扫普通依赖里的服务声明。
内容的提问来源于stack exchange,提问作者dfuchss
相关产品推荐
相关产品推荐

