bigywater.tech
Java · 272

编译器插件:把额外检查接入 Java 构建

想在编译期强制团队约定,又不想写注解处理器?javac 的 -Xplugin 可以让你直接访问语法树与类型信息。这篇写一个真实的小插件,看它在命令行与 Maven 里怎么被加载、怎么报告问题、怎么让构建失败。

返回码 0 → 1
同一个插件把参数 error 打开后,javac 的返回码变化
ANALYZE
插件检查调用的编译阶段(此时类型信息已就绪)
compilerArgs
Maven 里接入 -Xplugin 的配置位置

-Xplugin 与诊断

实测 · 本机执行输出
  javac -Xplugin:NoPrintln:返回码 0(警告不让编译失败),输出:
    [NoPrintln] init,参数 []
    [NoPrintln] 各阶段事件次数(到 Main 生成结束时为止,开始事件): PARSE=2 ENTER=2 ANALYZE=1 GENERATE=1 ANNOTATION_PROCESSING=0 ANNOTATION_PROCESSING_ROUND=0 COMPILATION=1
    [NoPrintln] 前 14 个事件:[S:COMPILATION, S:PARSE:Main.java, F:PARSE, S:PARSE:Helper.java, F:PARSE, S:ENTER:Main.java, S:ENTER:Helper.java, F:ENTER, F:ENTER, S:ANALYZE:Main, F:ANALYZE:Main, S:GENERATE:Main, F:GENERATE:Main]
    <jtmp>/j132w/app/demo/Main.java:4: warning: 不要在 Main 里用 System.out.println,请使用日志框架
            System.out.println("hello");
                              ^
    <jtmp>/j132w/app/demo/Helper.java:4: warning: 不要在 Helper 里用 System.out.print,请使用日志框架
            System.out.print(s);
                            ^
    2 warnings
  javac "-Xplugin:NoPrintln error"(插件参数 error → 报错):返回码 1;生成的 class 数 0
    [NoPrintln] init,参数 [error]
    <jtmp>/j132w/app/demo/Main.java:4: error: 不要在 Main 里用 System.out.println,请使用日志框架
    <jtmp>/j132w/app/demo/Helper.java:4: error: 不要在 Helper 里用 System.out.print,请使用日志框架
    2 errors
  "-Xplugin:NoPrintln allow=Main,Helper"(插件参数白名单):返回码 0,诊断条数 0
  不给 -processorpath、-cp 也没有插件:返回码 1 ; ['error: plug-in not found: NoPrintln', '1 error']
  只给 -cp(没有 -processorpath):返回码 0 ;警告数 2 (插件也能从 classpath 上找到)
  -Xplugin:NoSuchPlugin:返回码 1 ; ['[NoPrintln] init,参数 []', 'error: plug-in not found: NoSuchPlugin']

编译阶段事件

实测 · 本机执行输出
  各阶段事件次数(到 Main 生成结束时为止,开始事件): PARSE=2 ENTER=2 ANALYZE=1 GENERATE=1 ANNOTATION_PROCESSING=0 ANNOTATION_PROCESSING_ROUND=0 COMPILATION=1
  前 14 个事件:[S:COMPILATION, S:PARSE:Main.java, F:PARSE, S:PARSE:Helper.java, F:PARSE, S:ENTER:Main.java, S:ENTER:Helper.java, F:ENTER, F:ENTER, S:ANALYZE:Main, F:ANALYZE:Main, S:GENERATE:Main, F:GENERATE:Main]

编译成本

实测 · 本机执行输出
  两个小文件,javac 总耗时(7 次去掉前 2 次的中位):不带插件 233 ms;带 NoPrintln 插件 248 ms

Maven 接入

实测 · 本机执行输出
  mvn compile(插件以警告方式报告):返回码 0 ;Maven 输出里的相关行:
    [NoPrintln] init,参数 []
    [NoPrintln] 各阶段事件次数(到 Main 生成结束时为止,开始事件): PARSE=2 ENTER=2 ANALYZE=2 GENERATE=2 ANNOTATION_PROCESSING=0 ANNOTATION_PROCESSING_ROUND=0 COMPILATION=1
    [NoPrintln] 前 14 个事件:[S:COMPILATION, S:PARSE:Helper.java, F:PARSE, S:PARSE:Main.java, F:PARSE, S:ENTER:Helper.java, S:ENTER:Main.java, F:ENTER, F:ENTER, S:ANALYZE:Helper, F:ANALYZE:Helper, S:GENERATE:
    [WARNING] /mvn/src/main/java/demo/Helper.java:[4,25] 不要在 Helper 里用 System.out.print,请使用日志框架
    [WARNING] /mvn/src/main/java/demo/Main.java:[4,27] 不要在 Main 里用 System.out.println,请使用日志框架
    [INFO] BUILD SUCCESS
  fork=false(进程内的 Compiler API),参数写成一个字符串 "-Xplugin:NoPrintln error":返回码 1;相关行:
    [NoPrintln] init,参数 [error]
    [ERROR] /mvn/src/main/java/demo/Helper.java:[4,25] 不要在 Helper 里用 System.out.print,请使用日志框架
    [ERROR] /mvn/src/main/java/demo/Main.java:[4,27] 不要在 Main 里用 System.out.println,请使用日志框架
    [INFO] BUILD FAILURE
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project app: Compilation failure: Compilation failure:
    [ERROR] /mvn/src/main/java/demo/Helper.java:[4,25] 不要在 Helper 里用 System.out.print,请使用日志框架
    [ERROR] /mvn/src/main/java/demo/Main.java:[4,27] 不要在 Main 里用 System.out.println,请使用日志框架
  fork=true(外部 javac 进程),同样的写法:返回码 1;相关行:
    [INFO] [NoPrintln] init,参数 [error]
    [ERROR] /mvn/src/main/java/demo/Helper.java:[4,24] error: 不要在 Helper 里用 System.out.print,请使用日志框架
    [ERROR] /mvn/src/main/java/demo/Main.java:[4,26] error: 不要在 Main 里用 System.out.println,请使用日志框架
    [INFO] BUILD FAILURE
    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project app: Compilation failure: Compilation failure:
    [ERROR] /mvn/src/main/java/demo/Helper.java:[4,24] error: 不要在 Helper 里用 System.out.print,请使用日志框架
    [ERROR] /mvn/src/main/java/demo/Main.java:[4,26] error: 不要在 Main 里用 System.out.println,请使用日志框架

分步演示

javac 插件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 的 javac;Maven maven-compiler-plugin 3.13.0;插件只检查 System.out.println/print 两种调用
    • 没有验证:插件在大型项目上的耗时、增量编译下的行为(Maven 的增量检测可能让没有改动的文件跳过检查)、插件的 Gradle 接入、多模块项目里 -processorpath 的分发、插件抛异常时 javac 的表现、Error Prone 等成熟插件的使用
    京ICP备2026067502号-1京公网安备11010502063487号