拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JDK 21下Lombok导致NoSuchFieldError的修复指南

JDK 21下Lombok导致NoSuchFieldError的修复指南

在命令行敲下javac welcome.java,屏幕上没等来编译成功,反而甩出一长串java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'。第一次撞见这行东西的人,九成会先回头翻自己的welcome.java找语法毛病——白翻,这跟你写的代码基本没关系。报错里的com.sun.tools.javac.tree是 javac 自己的内部包,JCTree$JCImport是 javac 语法树里表示 import 语句的那个节点,qualid是挂在这个节点上的一个字段;整句话翻译成人话就是:有某个工具在运行期间拿着一张“旧地图”,去 javac 的语法树节点上摸字段,摸了个空,于是抛了NoSuchFieldError。真凶几乎从来不是业务代码,而是编译期被自动挂进来的注解处理器,最常见的是 Lombok,其次是一些会读 javac 内部结构的代码生成器、静态检查工具。

这篇我按“先定位、再定性、再动手”的顺序写:从命令行单文件编译的现场排查,到 Maven、Gradle、IDE 三条链路的完整修复,再到版本对应关系和几个我实打实踩过的坑。JDK 17 / 21 / 22 / 23 环境都覆盖,Java 新手和有几年经验的老手都能直接抄作业。

1. 先把这行报错拆开看,别急着改代码

1.1 报错原文其实是三截拼起来的

我习惯把这类堆栈切成三段读,效率比一行行扫高得多。第一截java.lang.NoSuchFieldError,这是Error家族里的成员,不是Exception,它代表“运行时发现某个字段不存在”,跟语法错误、类型错误完全不是一回事,编译器的语法检查阶段早过了。第二截Class com.sun.tools.javac.tree.JCTree$JCImport,中间那个$说明JCImport是JCTree的内部类,而JCTree是 javac 编译器的抽象语法树基类,也就是编译器自己用来描述你代码结构的“中间表示”。第三截does not have member field 'com.sun.tools.javac.tree.JCTree qualid'最有信息量:它不只报了字段名qualid,还把字段类型JCTree一起带上了。

这一点特别关键——很多人一看“does not have member field”就以为字段被删了,实际上字段还在,只是类型变了,而查找方是按“类 + 字段名 + 字段类型”三元组去定位的,类型对不上照样报同一个错。会输出这种中英混排句式(Class ... does not have member field ...)的,基本可以直接锁定 Lombok 的反射封装层,它内部用一个类似ShadowClass的机制去懒加载 javac 内部字段,找不到就用这句固定文案抛出来。所以看到这句英文,第一反应不该是“我的 JDK 装坏了”,而是“我引的某个跟编译器打交道的工具,版本太旧了”。

1.2 注解处理器是怎么被悄悄拉进编译流程的

这是整个问题里最容易被忽略的一环,也是最容易让人怀疑人生的地方:为什么我只编译一个 Hello World 也会炸?答案在javac的处理器发现机制上。当你在命令行执行javac Welcome.java时,如果没有显式指定-processorpath,javac 会把-cp指定的类路径(以及CLASSPATH环境变量)扫一遍,去找所有META-INF/services/javax.annotation.processing.Processor文件,找到就自动实例化并运行。

而lombok.jar里正好带着这个服务描述文件,也就是说只要你的CLASSPATH环境变量里挂着任何一个版本的 lombok.jar,哪怕你编译的是一个连注解都没有的类,javac 也会把 Lombok 的处理器唤醒。Lombok 在初始化阶段就要去语法树上取JCImport.qualid,JDK 版本不对,当场就抛。热词里那个javac welcome.java的场景,恰恰说明污染不在项目依赖里,而在这个人机器的全局环境上——这点后面 4.2 节会有专门的排查命令,三条就能定住。

顺便提醒一个基础坑:文件名和类名要一致。public class Welcome写在welcome.java里,javac 会直接报class Welcome is public, should be declared in a file named Welcome.java,Windows 的大小写不敏感文件系统也救不了你,因为 javac 是按名字字符串比对的。同理,类名是Welcome时你敲java welcome,在某些环境下会得到Could not find or load main class。建议文件名和类名一律保持完全一致,能省掉一类毫无技术含量的报错。

1.3 为什么首犯总是 Lombok

原因很直白:Lombok 的实现方式决定了它必须跟 javac 的内部结构强耦合。@Data、@Getter、@Builder这些注解不是靠代理或者字节码增强实现的,而是在编译期往 javac 的语法树里插方法、插构造器、插 getter/setter,插完再让 javac 继续正常生成字节码。要插就得先读,读的就是com.sun.tools.javac.tree这一堆内部类。问题是这些类从来没承诺过稳定——它们是 JDK 的私有实现,每个大版本都可能改字段、改类型、改方法签名。于是每一轮 JDK 升级,Lombok 团队都得跟着打补丁,用户升级 JDK 的速度和 Lombok 发布兼容版本的速度一旦错开,就会撞上今天这个错。

所以判断套路很简单:报错节点名里出现com.sun.tools.javac,优先盘查三类东西——注解处理器(Lombok、MapStruct 的 binding 包、部分代码生成器)、以-javaagent形式挂载的工具、以及直接声明依赖 jdk.compiler 内部 API 的静态检查工具。这三类查完,问题基本就现形了,不用去怀疑 JDK 安装包、也不用重装系统。

2. 根因定性:JDK 21 动了字段类型,旧处理器还在按老地图找路

2.1 那次字段类型变更与反射查找的匹配逻辑

真正引发这个报错的,是 JDK 21 对JCImport的一次内部调整:为了兼容import static a.b.C.method;这类单成员静态导入的语法形态,qualid字段的类型从原来的字段访问节点类型放宽成了通用的JCTree。字段名一个字没改,类型变了,这就是全部。

Lombok 1.18.30 之前版本的代码里,是按“老类型”去取这个字段的,反射查找的匹配条件里包含字段类型,找不到匹配项就抛出我们看到的这句NoSuchFieldError,而且报错文案里会把它自己认为的类型(com.sun.tools.javac.tree.JCTree)一并打出来,这也是为什么不同人看到的报错会有细微差别——JDK 版本和 Lombok 版本组合不同,打出来的类型串就可能不同。1.18.30 之后,Lombok 改成兼容两种形态:先按新类型找,找不到再退回旧类型,或者用别的路径绕开,报错自然就消失了。

这里面有个认知要摆正:这不是“你的代码不兼容 JDK 21”,而是“两个工具之间的契约变了”。你的业务代码一行都不用改,该改的是工具版本。明白了这点,就不会出现那种把import语句全删掉、把注解全注释掉的无效折腾。

2.2 JDK 与 Lombok 的版本对应关系表

下面这张表是我按官方发布记录和实际项目验证整理的,写项目方案时可以直接当速查用。记住一条原则就够:JDK 越新,Lombok 必须越新;拿不准的时候,去对应版本的发布说明里看第一行写的支持 JDK 范围,比搜索引擎上抄来的结论靠谱。

JDK 版本需要的最低 Lombok 版本说明
JDK 171.18.22 及以上LTS,生态最稳,1.18.24 以上基本无感
JDK 181.18.24 及以上过渡版本,实际项目里少见
JDK 19 / 201.18.26 ~ 1.18.28这两个版本支持窗口很短
JDK 211.18.30 及以上本文报错的高发组合,1.18.30 是分水岭
JDK 221.18.32 及以上与 21 的报错形态类似
JDK 231.18.34 及以上建议直接用当前最新稳定版
JDK 241.18.36 / 1.18.38 及以上新 JDK 一律选最新 Lombok

比 JDK 更隐蔽的是 Spring Boot 的版本管理。Spring Boot 的依赖 BOM 里会统一管 Lombok 版本,Spring Boot 2.7 那一代管的大约是 1.18.24,用它配 JDK 21 编译,必炸;Spring Boot 3.0 / 3.1 大概落在 1.18.26 ~ 1.18.28,同样炸;3.2 之后才跟上 1.18.30 以上。很多人是把老项目直接换了个 JDK 21 来跑,依赖一个没动,于是这个错就出现了。处理办法不是把 Spring Boot 整个升上去(牵动面太大),而是单独覆盖一个属性:

<properties> <java.version>21</java.version> <lombok.version>1.18.34</lombok.version> </properties>

Spring Boot 的依赖管理里本来就定义了lombok.version这个属性,你在自己 pom 里覆盖它,传递到maven-compiler-plugin的处理器路径也会跟着用新版本,改动面最小。这个技巧我用了很多次,比直接在某一个<dependency>上硬写版本号更干净,因为不会出现“依赖声明写的是新版本,处理器路径用的还是 BOM 里的旧版本”这种分裂状态。

3. 四条修复路径,按代价从低到高排

3.1 首选方案:把 Lombok 升上去(附 Maven / Gradle 完整配置)

这是代价最小、后患最少的做法,没有之一。升级完记得做一次彻底清理,因为增量编译缓存里可能还留着旧处理器跑出来的产物。

Maven 项目里,我建议同时做两件事:声明依赖时带上版本号并标provided+optional,以及在maven-compiler-plugin里显式配置annotationProcessorPaths。

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> <optional>true</optional> </dependency>

provided表示它只在编译和测试阶段可用,不会打进最终的运行包;optional表示不会传递给依赖你的下游项目。这两个标记合起来解决的是另一个常见毛病:Lombok 明明只是编译期工具,却被塞进了生产环境的胖 jar 里,被安全扫描工具当成隐患报出来。

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>

为什么要额外配annotationProcessorPaths?因为一旦你写了这段,Maven 会给 javac 传一个明确的-processorpath,javac 就不再去扫 classpath 找处理器了,处理器版本被钉死在这一个地方。这就把“classpath 上意外躺着一个旧 lombok.jar”这类问题从根上堵死了,是治本的一步。如果项目里还用了 MapStruct,就在这个列表里再加上lombok-mapstruct-binding,顺序和组合按 MapStruct 官方文档来,别自己拍脑袋排。

Gradle 项目里对应的是这两行,经常有人只写compileOnly忘了annotationProcessor,结果注解不生效,又回头怀疑 JDK:

dependencies { compileOnly 'org.projectlombok:lombok:1.18.34' annotationProcessor 'org.projectlombok:lombok:1.18.34' testCompileOnly 'org.projectlombok:lombok:1.18.34' testAnnotationProcessor 'org.projectlombok:lombok:1.18.34' }

3.2 次选方案:让这个工程用 JDK 17 编译,但别动全局 JAVA_HOME

如果项目一时半会儿升不了 Lombok(比如框架版本被锁死、有下游依赖),退一步用 JDK 17 编译也是合理选择。但我不建议去改系统环境变量里的JAVA_HOME,那会污染你机器上所有工程,今天修好这个,明天另一个新项目又乱了。

正确做法是用工具链(toolchain),只影响当前工程。Gradle 里三行搞定:

java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }

Maven 侧配合~/.m2/toolchains.xml声明本机 JDK 17 的位置,再用maven-toolchains-plugin或编译器插件较新版本提供的jdkToolchain参数绑定到工程上。核心思路就是:编译期用哪个 javac,是工程级配置,不是系统级配置。这么做的额外好处是,团队里每个人机器上装的 JDK 可能五花八门,但工程编译结果一致,CI 上也不用为了某个老项目专门换构建镜像。

需要特别提醒的是一个很多人会踩的假动作:-source 17 -target 17或者--release 17。这三个参数只影响“按哪个版本的语法和 API 来检查、生成什么版本的字节码”,真正跑起来的编译器还是你当前javac -version的那个。所以用 JDK 21 的 javac 配--release 17,JDK 21 的JCImport依然会被加载,报错一点不少。想让工具看到旧结构,必须让真正的 javac 换成 17,这就是 toolchain 和--release的本质区别。

3.3 应急方案:-proc:none和换编译器,只适合临时救场

javac -proc:none Welcome.java这一招在排查阶段非常好用,它的作用是直接关掉所有注解处理器的发现和执行。如果加上它编译就正常,去掉就报错,那你已经百分百确定了:问题出在处理器侧,跟 JDK 安装、跟你的代码都没有关系。这就是一个精准的“二分定位”手段。

但它不能当解决方案用。原因很简单:关掉处理器,Lombok 就不工作了,你代码里所有靠@Data、@Getter生成的 getter、构造器、equals全都不存在了,编译会从一个报错变成几十个“找不到符号”。同样地,在 IDE 里把编译器从 javac 切成 Eclipse 的编译器(IntelliJ 里有这个选项)确实能绕过这个错,因为 Eclipse 编译器根本不走com.sun.tools.javac这套结构,但命令行mvn compile照样炸,团队里其他人也照样炸。

注意:这两种手段的价值在于“快速判断方向”和“临时让 IDE 别飘红”,不要在提交记录里留下这类配置,更不要把它当成正式修复写进构建脚本。

3.4 关于--add-exports/--add-opens:先分清错误类型,别乱加

网上一搜这类报错,很容易搜到“加个--add-opens就好了”的答案,然后有人不分青红皂白往构建脚本里塞一堆参数。这里必须掰清楚:本次这个NoSuchFieldError说的是字段类型对不上,模块可见性根本不是原因,加导出参数解决不了它。真正需要--add-exports的是另一类错误,症状完全不同:

报错关键字含义处理方向
does not have member field字段名或类型不匹配,版本错位升级/降级注解处理器版本
package com.sun.tools.javac.tree is not visible模块未导出,编译期可见性问题--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
cannot access class ... class file for ... not found编译期引用了内部类但类路径缺失补依赖或改用公开 API
IllegalAccessError模块强封装拦截了运行时反射访问--add-opens jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
ExceptionInInitializerError处理器静态初始化失败看被包住的根因异常,通常是版本问题

只有当错误明确是“包不可见”或“反射被拦截”时,加导出/开放参数才有意义,而且它只是打开门,里面的结构该变还是变。写法上,Maven 项目可以放进编译器插件的参数列表,Gradle 放进options.compilerArgs:

<compilerArgs> <arg>--add-exports</arg> <arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> </compilerArgs>

我个人的态度是:这类参数是技术债的标记,能不用就不用。它让工程和 JDK 内部结构绑得更死,下一个 JDK 版本可能会以另一种方式炸给你看。

4. 从命令行到构建工具,完整实操一遍

4.1 最小复现工程:一个 welcome.java 就够

先写一个不带任何注解、不引任何依赖的小文件,用来验证环境本身是否干净。文件名建议用Welcome.java,和类名完全一致:

public class Welcome { public static void main(String[] args) { String name = args.length > 0 ? args[0] : "路人"; int code = args.length > 1 ? Integer.parseInt(args[1]) : 0; System.out.println("欢迎 " + name + ",你的编号是 " + code); System.out.println("当前 JDK:" + System.getProperty("java.version")); } }

编译运行两条命令,输出如下:

D:\mycode>javac Welcome.java D:\mycode>java Welcome a 44 欢迎 a,你的编号是 44 当前 JDK:21.0.2

我特意把java.version打出来,因为排查这类问题时“当前到底哪个 JDK 在跑”是最基础也最常搞错的信息。如果这一步就报NoSuchFieldError,请立刻往下看 4.2 节,一定是环境里挂了东西;如果这一步干净通过,说明问题在项目依赖侧,直接跳到 4.3 节。

4.2 三条命令定住凶器:环境变量排查

第一个要看的是CLASSPATH,它是最隐蔽的污染源。很多人若干年前为了跑某个库配过一次,之后就再没管过,一直挂在系统变量里:

# Windows echo %CLASSPATH% echo %JAVA_TOOL_OPTIONS% echo %_JAVA_OPTIONS% # Linux / macOS echo $CLASSPATH echo $JAVA_TOOL_OPTIONS echo $_JAVA_OPTIONS

CLASSPATH里如果出现任何lombok.jar路径,就是它;JAVA_TOOL_OPTIONS或_JAVA_OPTIONS里如果出现-javaagent:lombok.jar,同样会让每次启动 javac 时都把 Lombok 的 agent 挂上。后者通常是曾经为了让 IDE 支持 Lombok 而配的“全局方案”,现在成了定时炸弹。清理方式就是把这些变量里跟 Lombok 相关的部分删掉,或者干脆把变量清空,然后重开一个命令行窗口(环境变量改了不重开窗口不生效,这一步经常被人忽略)。

第二个动作是做二分验证:

javac -version javac -proc:none Welcome.java javac Welcome.java

如果第一条显示 21 及以上、第二条通过、第三条报错,结论已经出来了:注解处理器侧有旧版本在捣乱。接下来无论是命令行还是 Maven,都按 3.1 节的方案把版本对齐即可。这套流程我一般五分钟内就能走完,比在网上东拼西凑试参数快得多。

4.3 Maven 工程的完整落地步骤

假设你手上是个 Spring Boot 项目,JDK 21,mvn clean compile报这个错。按下面顺序走。

先看依赖树里到底解析出了哪个版本,这是最关键的一步,别猜:

mvn dependency:tree -Dincludes=org.projectlombok:lombok

如果输出里出现了两行不同版本号,说明有传递依赖带了旧版本进来(常见于某些脚手架、内部公共包)。这时候在你自己的 pom 里显式声明${lombok.version}就能收口,因为直接依赖优先于传递依赖。如果只出现一行但是 1.18.28 及以下,就按 2.2 节的方法覆盖属性。

然后补齐annotationProcessorPaths配置(见 3.1 节),再执行一次彻底清理:

mvn clean mvn -U compile

-U是为了强制刷新快照和元数据,避免本地仓库里缓存的旧描述文件继续干扰。如果编译通过,再跑一次mvn -U test确认测试阶段也没问题——测试编译使用的是另一套 classpath 配置,偶尔会出现主代码通过、测试代码失败的情况。

最后做个回归验证:随便找个用@Data的类,mvn clean package之后反编译或者直接看target/classes里的 class 文件,确认 getter 确实生成了。这一步能避免“编译不报错了,但注解悄悄失效”的隐性故障,我就遇到过有人把annotationProcessorPaths配错,编译通过但 Lombok 完全不生效,运行时报NoSuchMethodError。

4.4 Gradle 工程的两处关键配置

Gradle 侧的问题通常出在配置项写漏或者 toolchain 与 daemon 不一致。第一处是依赖配置要成对出现,compileOnly和annotationProcessor缺一不可,测试环境还要单独再来一遍,前面 3.1 节已经给过。第二处是 toolchain 与 Gradle 守护进程的 JDK 可能不是同一个,这一点最容易把人绕晕:

java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }

如果你声明了 toolchain 21,但 Gradle 守护进程本身跑在 JDK 17 上,注解处理器会被加载进 17 的环境里执行,此时你看到的报错信息可能和 JVM 版本对不上号,很容易误判。排查办法是打印实际使用的编译器信息,或者直接查看依赖解析结果:

gradle dependencies --configuration annotationProcessor gradle -q javaToolchains

javaToolchains会列出 Gradle 探测到的所有 JDK,包括版本、厂商和路径,能一眼看出它到底选了哪个。这一步确认清楚,能省掉很多“配置明明写对了却没用”的困惑。

4.5 IDE 侧要动的三处设置

命令行修好了,IDE 里还飘红,这种情况太常见了,因为 IDE 有自己的一套编译配置,跟构建工具的配置并不完全共享。

第一处是 Project SDK 和 Module SDK,确认它们指向的 JDK 版本跟构建脚本里声明的一致。第二处是注解处理开关,以 IntelliJ 为例,在设置里的编译器 → 注解处理器页面,确认“启用注解处理”被勾上,并且处理器路径是“从项目 classpath 获取”或者显式指向了正确版本的 Lombok。第三处是清理动作,改完配置后一定要执行一次完整的重新构建,而不是让它增量编译——增量编译会把旧的 class 文件和缓存的语法树残留留在target或build目录里,症状会看起来像“改了没用”。

顺手再说一个高频陷阱:IDE 的 Lombok 插件只负责让编辑器不飘红(识别生成的 getter),它不参与实际编译逻辑。有人以为把插件升到最新就万事大吉,结果命令行依然报错。插件版本和依赖版本是两件事,都要看,但真正决定编译行为的是依赖和处理器路径那一份。

5. 常见问题速查表与踩坑实录

5.1 症状、根因与处理对照表

现象大概率根因处理动作
单文件javac编译就报NoSuchFieldErrorCLASSPATH或JAVA_TOOL_OPTIONS里挂了旧 lombok清理环境变量,重开终端
Maven 编译报错但依赖写了新版本BOM 里的属性覆盖了你的声明覆盖lombok.version属性
依赖树里出现两个 Lombok 版本传递依赖带进旧版本直接依赖钉版本,收口传递
IDE 编译通过,命令行报错IDE 用了内置或插件侧的处理器统一两者版本并重建
加-proc:none后正常处理器版本与 JDK 不匹配升级处理器,而不是保留该参数
加了--add-opens依然报同一句错错误类型判断错,字段类型不匹配不是可见性问题回到版本对齐主线
升级后仍然报错增量缓存未清干净清target/ 重新完整构建

5.2 几个我实际踩过的坑

第一个坑是“全局 javaagent 的历史遗留”。很早以前为了让某个编辑器识别 Lombok,我把-javaagent配到了全局变量里,当时一切正常。两年后升到 JDK 21,任何项目的javac都开始报这个错,排查了整整一个下午,最后是在环境变量里发现的。教训是:凡是跟编译工具链相关的东西,一律放在工程级配置里,永远不要放在系统级环境变量里。

第二个坑是“依赖树看起来干净,处理器路径却还是旧的”。有些脚手架生成的 pom 会把处理器配置写死在一个父 pom 的 profile 里,你本地看依赖树只有一个版本,实际编译时走的是另一套annotationProcessorPaths。排查方式是在构建时加-X打开调试输出,搜索 lombok 关键字,看它实际加载的 jar 路径是哪一个,比看依赖树更接近真相。

第三个坑是“--release误以为能降级编译器”,前面 3.2 节说过了,这里再强调一次,因为它真的太常见了。字节码目标版本和编译器实现版本是两个维度,前者管兼容性,后者管工具看到的结构长什么样,本问题只跟后者有关。

第四个坑是“只升级 Lombok 却忘了测试作用域”。主代码编译通过了,mvn test还是报同样的错,因为测试编译走的是测试 classpath,那里的处理器配置需要单独确认。省事的做法是把版本属性统一在父 pom 里定义,主代码和测试代码都引用同一个属性,一处改动处处生效。

第五个坑是“换 JDK 前不看生态”。每次跨大版本升 JDK,我都会先做一件事:把构建插件、注解处理器、字节码工具(覆盖率、mock、字节码增强类库)的兼容版本列一张表,逐个核对。这张表花二十分钟做,能省掉后面两天的问题排查。这个习惯比任何单个修复技巧都值钱。

6. 把这类问题挡在门外:几条能落地的工程约束

6.1 版本统一与依赖审计

最有效的一条约束是:所有编译期工具的版本只在父 pom 的属性区出现一次,子模块和插件配置一律引用属性,不允许在任何地方硬编码版本号。这样做的直接收益是,升级只需要改一个地方,不会出现“依赖声明是新的、处理器路径是旧的”这种分裂。配合mvn dependency:tree的过滤输出,把它加进 CI 流水线里做成检查项,一旦出现两个 Lombok 版本就构建失败,问题在提交阶段就被拦下来了。

另一条是给工具链上锁。无论 Maven 还是 Gradle,都在工程里显式声明编译用的 JDK 版本,不要让构建结果依赖“开发机上装了什么”。CI 环境尤其要跟上,因为很多线上翻车都是“本地能跑到流水线上就炸”或者反过来的场景,根源都是两地 JDK 不一致。

6.2 升级 JDK 的标准动作清单

我现在升 JDK 大版本基本按这个顺序走:先查目标 JDK 的发布说明里有哪些内部 API 变更(javac 相关的条目重点看);再核对注解处理器、构建插件、静态检查工具的兼容版本;然后在本地用最小复现工程编译一次,确认没有com.sun.tools.javac相关的报错;最后才动业务工程,并且分模块灰度,先跑通一个模块再推全量。

这套流程听起来麻烦,但比在业务代码里到处注释掉注解、删掉 import 语句来“验证是不是编译器的问题”要理性得多。毕竟我们从一开始就说了,这类报错从来不是你代码写错了,是你的工具之间版本没对齐——那么解决问题的动作,自然也应该发生在工具层面,而不是代码层面。

返回列表