Modernizer 拿 javaVersion 当尺子:JDK 8 升 17 时怎么接才不白跑
仓库:gaul/modernizer-maven-plugin(Apache-2.0,v3.4.0,Maven Central 坐标 org.gaul:modernizer-maven-plugin)。它的定位是:Detect uses of legacy Java APIs which modern Java versions supersede。
环境要求:Maven 3.6.3+;运行插件用 JDK 8+;从源码构建插件用 JDK 17+。
判定方式:javaVersion 是尺子,不是开关
插件用 ASM 读字节码做内省,不解析源码。默认规则集覆盖 400+ 个遗留 API,包括 Apache Commons、Guava、Joda-Time 等第三方库。
每条规则带一个 version,判定条件是 **`rule.version org.gaul
modernizer-maven-plugin
3.4.0
${maven.compiler.release}
modernizer
verify
modernizer
`mvn modernizer:modernizer` 是单次执行;只有绑到 `verify`,正常构建流程里才会跑。
## javaVersion 绑定 release 属性
不要硬编码 `17`,而是绑 `${maven.compiler.release}`(或 `${maven.compiler.target}`),让检查目标和编译目标同源。
多 module 与父 pom 场景:在父 pom 里把 `17` 定义成属性,插件放进 `
`,子模块继承。注意 `${maven.compiler.release}` 是普通属性占位符,如果你只在 `maven-compiler-plugin` 的 `` 里写了版本而没有定义同名属性,占位符不会被自动填上;要么显式定义属性,要么在子模块里单独覆盖 ``。混合 JDK 的仓库(部分模块留在 8)就在子模块里覆盖该属性,否则复用父 pom 的值会把子模块按 17 去量。
## 三个默认值
- `failOnViolations` 默认 `true`:列完违规按总数让 phase 失败。命令行可用 `-Dmodernizer.failOnViolations` 覆盖。
- `includeTestClasses` 默认 `true`:测试类也会被扫。老项目测试里 Guava 用得往往比主代码还多,第一轮跑出来的量要有心理准备。
- `violationsFile` 是**替换**而不是补充:一旦指定,标准 400+ 条检查全部禁用。要自定义又保留默认规则,用 `violationsFiles` 多文件叠加,后面的覆盖前面的(覆盖范围含 `violationsFile` 和默认规则)。路径可加 `classpath:/` 前缀。
`-Dmodernizer.skip` 可整体跳过。
## 排除项的四种写法,格式不一样
```xml
java/lang/String.getBytes:(Ljava/lang/String;)[B
exclusionsFile/exclusions:按 javap 格式关闭单条规则,类型写java/util/Vector,方法带签名。空行和#开头的行忽略。exclusionPatterns:正则,匹配 javap 形式,例如java/lang/.*。ignorePackages:包前缀,写foo.bar会连带忽略foo.bar.*、foo.bar.baz.*。ignoreClassNamePatterns:全限定类名正则,包路径用/分隔(ASM 格式)。
容易混的点:exclusions 走 javap 的点号方法名写法,而 ignoreClassNamePatterns 走斜杠格式;前者精确、后者是正则。写错格式不会报错,只是匹配不上,看起来像"规则没生效"。
另外 ignoreGeneratedClasses 会跳过注解简单名恰为 "Generated"、retention 为 RUNTIME 或 CLASS 的类/方法/构造器,典型是 Lombok 在 lombok.addLombokGeneratedAnnotation=true 时生成的成员。javax.annotation.Generated 和 javax.annotation.processing.Generated 的 retention 都是 SOURCE,编译器阶段已丢弃,识别不到。
@SuppressModernizer 的边界
引入 org.gaul:modernizer-maven-annotations:3.4.0,然后把 @SuppressModernizer 标在类、构造器、方法或类型使用处。插件按简单名匹配,任何包下同名注解都生效,官方依赖只是规范副本。
字段和包不能抑制——@Target 不含它们。这类位置只能落到 exclusions 或 ignoreClassNamePatterns。
换过去不等价的地方
规则抽样里有几条注释值得单独记住,机械替换会改行为:
Maps.immutableEntry→Map.entry(Object, Object),version 9:后者拒绝 null key 和 value。Ints.checkedCast(long)→Math.toIntExact(long),version 8:超出范围时抛ArithmeticException,不是原来的IllegalArgumentException。BaseEncoding.base16()→HexFormat.of().withUpperCase(),version 17:HexFormat.parseHex接受小写数字,而原来的 decode 不接受。Lists.reverse(List)→List.reversed(),version 21:目标 21 才可用。IntMath.checkedPow(int, int)→Math.powExact(int, int),version 25:目标 17/21 时这条不会报。
同批里相对直接的:ByteStreams.toByteArray → InputStream.readAllBytes()、ByteStreams.copy → InputStream.transferTo(OutputStream)、Charsets.UTF_8 → StandardCharsets.UTF_8、Lists.newArrayList() → ArrayList<>()、Maps.newHashMapWithExpectedSize → HashMap.newHashMap(int)(version 19)、UnsignedInts.compare → Integer.compareUnsigned、com.google.common.base.Function → java.util.function.Function。另外 Collections.sort 建议改成 List.sort,String.getBytes(String) 改成 String.getBytes(Charset)。
输出接 GitLab 代码质量
默认 CONSOLE,一行一条,带源码位置和建议:
[ERROR] src/main/java/org/example/Foo.java:42: Prefer java.util.ArrayList<>()
[ERROR] src/main/java/org/example/Foo.java:57: Prefer java.nio.charset.StandardCharsets.UTF_8
violationLogLevel 取 ERROR/WARN/INFO/DEBUG,默认 ERROR;failOnViolations 为 true 时列完按总数失败。
接 GitLab 代码质量报告用:
CODE_CLIMATE
MINOR
outputFormat=CODE_CLIMATE 输出符合 Code Climate 规范的 JSON;outputFile 默认 ${project.build.directory}/code-quality.json;codeClimateSeverity 取 INFO/MINOR/MAJOR/CRITICAL/BLOCKER,默认 MINOR。
与相邻工具的分工
同一类问题还有几套工具:Google Error Prone 的 JdkObsolete、Policeman's Forbidden API Checker、Checkstyle 的 IllegalInstantiation/Regexp(可模拟部分功能)、javac -Xlint:deprecated(只看 @Deprecated 标注)、Overstock library-detectors(看 @Beta)。Gradle 侧有 gradle-modernizer-plugin。
选一套当主判据即可。Modernizer 和 forbidden-apis 都会让构建失败、都靠签名匹配,同时装两套容易出现同一条 API 两处告警、两份排除清单要各自维护,规则版本语义还不同——先统一在一处,比并行跑省事。