一次 Lombok 静默失效排查:JDK 23 注解处理默认策略变更引发的满屏「找不到符号」

0. TL;DR

结论先行:当构建突然报出成百上千个 找不到符号: 方法 getXxx(),而代码一行没改时,第一件事是执行 mvn -v 确认它用的是哪个 JDK,而不是去读代码。

读完可获得:

  • 一条从"报错表象"反推到"构建环境"的定位路径,以及为什么这类问题的报错离根因特别远;
  • JDK 23 注解处理默认策略变更的准确含义与影响面(它比"Lombok 不支持新 JDK"更根本);
  • Lombok 与 JDK 的版本对应关系,以及 Lombok 为什么对 JDK 版本如此敏感;
  • 一组让这类问题不再复发的工程配置。

本文所有结论均在下列环境实测得出:

组件 版本
Maven 3.9.16(Homebrew 安装)
构建 JDK(问题态) OpenJDK 26.0.1(Homebrew)
构建 JDK(正常态) Temurin 17.0.19
Lombok 1.18.24 / 1.18.30 / 1.18.32
maven-compiler-plugin 2.3.2
编译目标 source/target = 1.8
操作系统 macOS 15.6 (arm64),zsh

1. 背景与问题

一个多模块 Maven 工程,git status 干净,代码没有任何改动,mvn install 却直接失败,输出上百行编译错误。挑三条有代表性的(已脱敏):

text 复制代码
错误: 找不到符号
  符号:   方法 getBankMark()
  位置: 类 PaymentBankMarkEnum

错误: 无法将类 AmountBO 中的构造器 AmountBO 应用到给定类型;
  实际参数列表和形式参数列表长度不同

错误: 方法引用无效
  位置: 类 ValuePair

这三类错误表面上毫无关联:一个是枚举缺方法,一个是构造器签名对不上,一个是方法引用失效。分布在几十个不同的类里,看起来像是有人大范围改坏了代码。

git log 显示最近一次提交是几天前的合并,且当时 CI 是绿的。代码没变,构建结果变了,那么变的一定是构建环境。

2. 术语与前置约定

只列读懂本文必需的几个概念:

  • 注解处理(Annotation Processing):JSR 269 定义的编译期扩展机制。javac 在编译过程中发现并调用注解处理器,处理器可以生成新的源文件。Lombok 是其中最广为人知的使用者,但它的用法超出了规范范围,详见第 5 节。
  • -proc 选项 :javac 控制注解处理行为的开关,三个取值 none / only / full,分别表示不处理、只处理不编译、处理并编译。这个选项的默认值在 JDK 23 发生了变更,是本文的核心。
  • 构建 JDK 与运行 JDK :编译代码用的 JDK 和运行程序用的 JDK 是两个独立的东西,可以不同。本文说的"JDK 版本"若无特别说明,均指构建 JDK
  • 下文 javac 特指命令行编译器本身,编译器插件 特指 maven-compiler-plugin。

3. 整体视图

先把从"敲下 mvn install"到"产出字节码"的完整链路理清楚,再看每一段发生了什么。这条链上有两个不同的断点,它们的表现差异极大。

flowchart TD A[&#34;shell 环境<br/>JAVA_HOME 是否设置&#34;] --> B[&#34;Homebrew mvn 包装脚本<br/>JAVA_HOME 为空则兜底用自带 openjdk&#34;] B --> C[&#34;实际构建 JDK<br/>以 mvn -v 输出为准,不是 java -version&#34;] C --> D[&#34;maven-compiler-plugin 2.3.2<br/>只传 -source/-target,不传 --processor-path&#34;] D --> E{&#34;javac 注解处理默认策略&#34;} E -->|&#34;JDK 小于等于 22:默认开启&#34;| F{&#34;Lombok 版本是否支持该 JDK&#34;} E -->|&#34;JDK 大于等于 23:默认关闭&#34;| G[&#34;断点 1:处理器从未被调用<br/>零告警零提示&#34;] F -->|&#34;支持&#34;| H[&#34;改写 AST,生成 getter/setter/构造器<br/>编译成功&#34;] F -->|&#34;不支持&#34;| I[&#34;断点 2:ExceptionInInitializerError<br/>有明确堆栈&#34;] G --> J[&#34;字节码缺失全部生成方法<br/>满屏 找不到符号 getXxx()&#34;]

这张图要说的是:同样是"Lombok 没生效",断点 1 和断点 2 的可观测性天差地别。 断点 2 会甩一段明确指向 Lombok 的堆栈,五分钟就能定位;断点 1 什么都不说,只留下一地"找不到符号",让人误以为是业务代码坏了。本次遇到的正是断点 1。

后面各节按这条链逐段展开。

4. 定位过程

4.1 从三类报错反推共性

第一步不是打开报错的类去看,而是问:这三类错误有没有共同的解释?

把每类错误对应的源码拉出来看:

java 复制代码
// 报错 1 对应的源码:枚举上标了 @Getter
@Getter
public enum PaymentBankMarkEnum {
    CASH("cash"), WECHAT("wechat");
    private final String bankMark;   // getBankMark() 由 @Getter 生成
}

// 报错 2 对应的源码:类上标了 @AllArgsConstructor
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AmountBO {
    private Amount left;
    private Amount right;
    public static AmountBO zero() {
        return new AmountBO(Amount.newZero(), Amount.newZero());  // 两参构造器由 @AllArgsConstructor 生成
    }
}

// 报错 3 对应的源码:方法引用指向 @Data 生成的 getter
list.stream().map(ValuePair::getFirst).collect(Collectors.toList());

共性一目了然:三类错误缺失的符号,全部是 Lombok 应该生成而没有生成的成员。 缺 getter、缺全参构造器、方法引用指向的 getter 不存在------它们是同一个原因的三种表现。

到这里问题就从"几十个类同时坏了"收敛成了"Lombok 没跑"。这一步很关键:报错数量多不等于问题多,先找共性再动手,能省掉大量无效阅读。

4.2 mvn -vjava -version 不是一回事

Lombok 没跑,通常只有两种可能:依赖没了,或者编译环境不对。依赖用 mvn dependency:tree 一查还在(org.projectlombok:lombok:jar:1.18.30:compile),那就看环境:

console 复制代码
$ java -version
openjdk version "17.0.19" 2026-04-21
OpenJDK Runtime Environment Temurin-17.0.19+10

$ mvn -v
Apache Maven 3.9.16
Java version: 26.0.1, vendor: Homebrew, runtime: /opt/homebrew/Cellar/openjdk/26.0.1/...

java -version 是 17,mvn -v 是 26。

这两个值来自不同的解析逻辑:javaPATH,而 Maven 走 JAVA_HOME。当 JAVA_HOME 为空时,Homebrew 安装的 Maven 有一个包装脚本会兜底:

bash 复制代码
$ cat /opt/homebrew/bin/mvn
#!/bin/bash
JAVA_HOME="${JAVA_HOME:-/opt/homebrew/opt/openjdk/libexec/openjdk.jdk/Contents/Home}" \
  exec "/opt/homebrew/Cellar/maven/3.9.16/libexec/bin/mvn" "$@"

Homebrew 的 maven formula 依赖 openjdk,装 Maven 时顺带装了一个当时最新的 JDK,并在 JAVA_HOME 缺省时指向它。没有人主动选择用 JDK 26 构建这个项目,它是包管理器的依赖传递带进来的。

这类"包管理器悄悄换掉你的运行时"的情况不止 Homebrew 有。所以排查构建问题时,mvn -v 的输出比 java -version 更有参考价值。

4.3 最小复现

有了怀疑对象就要做实验验证,而不是直接改配置试。构造一个不依赖任何工程上下文的最小用例:

java 复制代码
import lombok.Data;

@Data
public class Demo {
    private String name;

    public static void main(String[] args) {
        Demo d = new Demo();
        d.setName("hi");
        System.out.println(d.getName());
    }
}

绕开 Maven,直接用两个 JDK 的 javac 编译,变量只有 JDK 版本这一个:

console 复制代码
$ /path/to/temurin-17/bin/javac -cp lombok-1.18.30.jar -d out17 Demo.java
(无任何输出,编译成功)

$ /path/to/openjdk-26/bin/javac -cp lombok-1.18.30.jar -d out26 Demo.java
Demo.java:9: 错误: 找不到符号
        d.setName("hi");
         ^
  符号:   方法 setName(String)
  位置: 类型为Demo的变量 d
Demo.java:10: 错误: 找不到符号
        System.out.println(d.getName());
                            ^
  符号:   方法 getName()
  位置: 类型为Demo的变量 d
2 个错误

复现成立,且与 Maven、与项目代码全都无关。顺带把本地有的几个 Lombok 版本都在 JDK 26 上跑一遍:

Lombok 版本 JDK 17 JDK 26
1.18.24 成功 找不到符号
1.18.30 成功 找不到符号
1.18.32 成功 找不到符号

4.4 为什么一句告警都没有

到这里,"换 JDK 就好"已经够解决问题了。但有个现象值得追问:Lombok 挂掉是件大事,为什么 javac 一句话都不说?

先排除是不是自己没看见。打开注解处理相关的诊断开关:

console 复制代码
$ /path/to/openjdk-26/bin/javac -Xlint:processing -cp lombok-1.18.30.jar -d outx Demo.java
Demo.java:9: 错误: 找不到符号
(...输出与不加该选项时完全一致,没有任何关于注解处理器的信息)

-Xlint:processing 本应报告注解处理器的发现与执行情况,结果一个字都没有。这说明不是处理器执行失败,而是 javac 根本没有进入注解处理流程。

原因是一处 JDK 层面的默认行为变更。从 JDK 23 开始,javac 不再默认启用注解处理:

在 JDK 22 及之前,注解处理是默认启用的。从 JDK 23 起,如果没有提供 -processor--processor-path--processor-module-path 中的任何一个,就必须显式给出 -proc:only-proc:full,否则不会执行注解处理。

官方给出的动机是:让构建产出对"classpath 上无意引入的注解处理器"更健壮。这个特性 2006 年引入时默认开启是合理的,但在依赖动辄数百个 jar 的今天,任何一个 jar 只要在 META-INF/services 里注册了处理器,就能在编译期执行代码并影响产物------默认开启不再是个安全的选择。

JDK 21 和 22 为此做了过渡:当检测到"依赖默认策略的隐式注解处理"时会打印一条提示信息。同时 -proc:full 选项被向后移植到了 JDK 8u、11u、17u,让项目可以提前适配。

于是本次的完整因果链是:

  1. JAVA_HOME 为空 → Homebrew 包装脚本兜底用 JDK 26;
  2. maven-compiler-plugin 2.3.2 是 2011 年的插件,只传 -source / -target,不会传 --processor-path,也不认识 -proc:full
  3. JDK 26 因此按默认策略不启用注解处理
  4. Lombok 从未被调用,也就没有机会报错;
  5. 编译继续进行,产出缺少全部 Lombok 生成成员的字节码;
  6. 引用这些成员的地方全部报"找不到符号"。

报错离根因隔了六层,这就是它难查的原因。

4.5 用 -proc:full 逼出真实堆栈

既然默认策略把注解处理关掉了,那显式打开会怎样?这一步的价值在于验证"是否只是开关问题":

console 复制代码
$ /path/to/openjdk-26/bin/javac -proc:full -cp lombok-1.18.30.jar -d outf Demo.java
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release

批注处理程序抛出未捕获的异常错误。
java.lang.ExceptionInInitializerError
    at lombok.javac.apt.LombokProcessor.placePostCompileAndDontMakeForceRoundDummiesHook(LombokProcessor.java:174)
    at lombok.javac.apt.LombokProcessor.init(LombokProcessor.java:96)
    at lombok.core.AnnotationProcessor.init(AnnotationProcessor.java:213)
    at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$ProcessorState.<init>
    ...
Caused by: java.lang.NoSuchFieldException: com.sun.tools.javac.code.TypeTag :: UNKNOWN
    at lombok.permit.Permit.getField(Permit.java:144)
    at lombok.javac.JavacTreeMaker$SchroedingerType.getFieldCached(JavacTreeMaker.java:171)
    at lombok.javac.Javac.<clinit>(Javac.java:186)

开关打开后,问题从"静默"变成了"响亮",但并没有被解决。 真实原因浮出水面:Lombok 1.18.30 通过反射去取 com.sun.tools.javac.code.TypeTagUNKNOWN 字段,这个字段在 JDK 26 的 javac 里已经不存在了。

也就是说,JDK 26 上有两个独立的问题叠加:

  1. 注解处理默认关闭(JDK 23 起的策略变更)------这是导致"静默"的原因;
  2. Lombok 1.18.30 不认识 JDK 26 的 javac 内部结构------这是即便打开开关也跑不起来的原因。

只解决第一个不够。要在 JDK 26 上用 Lombok,两个都得解决。

补充一个实测结论:--processor-path-proc:full 效果相同,都能重新激活注解处理,得到上面同样的堆栈。这一点在第 6 节的配置建议里会用到。

5. 设计思想与权衡

排查到此结束,但更有价值的部分在于:这两个决策各自都是合理的,为什么叠加起来会产生一个如此难查的现象。

5.1 Lombok 的能力来源就是它的脆弱性来源

JSR 269 规定的注解处理器只能做一件事:生成新的源文件。规范明确不支持修改已有的抽象语法树------这是有意的设计约束,为的是保证编译过程可预测、可重入。

但 Lombok 要做的恰恰是修改已有类:往 Demo 类里塞 getName(),而不是生成一个新的 DemoGetter 类。这件事规范内做不到,所以 Lombok 选择了另一条路:注册成一个注解处理器拿到编译期的执行时机,然后用反射直接操作 javac 内部的 AST。

上面那段堆栈就是这条路径的完整暴露:

text 复制代码
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit
Caused by: java.lang.NoSuchFieldException: com.sun.tools.javac.code.TypeTag :: UNKNOWN
    at lombok.javac.JavacTreeMaker$SchroedingerType.getFieldCached

lombok.permit.Permit 这个类的职责就是绕过 JDK 的访问控制(用 sun.misc.Unsafe 改字段偏移量来关掉 AccessibleObject 的检查)。JavacTreeMaker 则是对 javac 内部 TreeMaker 的反射封装。

这个设计的 trade-off 非常清晰:

  • 换来的:能力上限突破了规范限制,实现了规范内无法实现的功能,这是 Lombok 得以存在的全部前提;
  • 付出的 :它依赖的是 JDK 的内部实现细节 ,而不是任何公开契约。com.sun.tools.javac.* 从 JDK 9 起就被模块系统封装了,Lombok 靠 --add-opensUnsafe 硬闯进去。JDK 内部随时可以重构,而重构一次 Lombok 就要跟一次;
  • 边界Lombok 与 JDK 之间不存在"向前兼容"。 一个 Lombok 版本只保证支持它发布时已存在的 JDK。这不是 Lombok 做得不好,是这条技术路线的必然结果。

理解了这一点,Lombok 的版本发布节奏就变得可预测了:

JDK 首个支持它的 Lombok 版本 发布日期
JDK 21 1.18.30 2023-09-20
JDK 22 1.18.32 2024-03-20
JDK 23 1.18.36 2024-11-15
JDK 24 1.18.38 2025-03-31
JDK 25 1.18.40 2025-09-04
JDK 26 1.18.46 2026-04-22

每个 JDK 大版本都对应一次 Lombok 适配。本次事故中项目用的 1.18.30 对应的上限正是 JDK 21,而实际构建 JDK 是 26,隔了五个大版本。

5.2 JDK 关闭默认注解处理:优化了什么,牺牲了什么

站在 JDK 的角度,"classpath 上出现注解处理器就自动执行它"是一个 2006 年做出的默认决策。当时的依赖规模和今天完全不是一个量级。

这个默认值的实际含义是:任何一个进入 classpath 的 jar,只要在 META-INF/services/javax.annotation.processing.Processor 里注册自己,就能在编译期执行任意代码,并影响最终产物。 传递依赖引入的 jar 也算。这既是构建可重现性问题,也是供应链攻击面。

改成默认关闭,优化的是构建的可预测性和安全性,牺牲的是兼容性------所有依赖隐式注解处理的构建都会在升级到 JDK 23 时行为改变,而且改变的方式是"静默地少做一件事"。

JDK 团队显然清楚这个代价,所以做了两年过渡:JDK 21、22 打印提示信息,-proc:full 向后移植到 8u/11u/17u。这套过渡方案对持续跟进 JDK 版本的项目是有效的。它失效的场景恰恰是本文这种:项目长期停在 JDK 17,从未见过 JDK 21/22 的提示,然后被包管理器一步跨到 JDK 26。

5.3 两个正确的决策叠加出一个错误的体验

这才是这次事故真正值得记录的地方。

  • Lombok 用反射操作 javac 内部:在它的约束下是合理选择;
  • JDK 关闭默认注解处理:在安全性考量下是合理选择;
  • 但两者叠加的结果是:Lombok 失效时,连报错的机会都没有。

如果只有 Lombok 的版本不兼容问题(断点 2),报错是 ExceptionInInitializerError 加完整堆栈,五分钟定位。如果只有默认策略变更(断点 1)而用的注解处理器是规范内的代码生成器,那缺失的是新生成的类,报错同样直指要害。

偏偏 Lombok 生成的是已有类的成员,它不生效的表现就是"这些成员从来没存在过"------与"你写错了代码"在编译器看来完全无法区分。

可迁移的判断:当一个组件的失效模式是"少做了一件事"而不是"做错了一件事"时,它的可观测性会急剧下降。 设计任何"默认启用、可被关闭"的机制时,关闭路径都应该留下痕迹。JDK 21/22 的提示信息正是为此而加,但它在跨版本跳跃时会被整段跳过------过渡期告警只对逐版本升级的用户有效,这是这类迁移方案的固有盲区。

6. 实践与避坑

按影响面从大到小排列,每条都说明"不做会怎样"。

6.1 JAVA_HOME.zshenv,不要放 .zshrc

根因是 JAVA_HOME 为空。但仅仅把 export JAVA_HOME=... 写进 ~/.zshrc 是不够的,因为 zsh 的启动文件加载时机不同:

文件 交互式终端 非交互式(脚本 / IDE / CI / 工具子进程)
.zshenv 加载 加载
.zprofile 加载(登录 shell) 不加载
.zshrc 加载 不加载

实测对比,配置只写在 .zshrc 时:

console 复制代码
$ zsh -i -c 'echo $JAVA_HOME'
/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

$ zsh -c 'echo "[$JAVA_HOME]"'
[]

不做会怎样 :手敲 mvn 正常,但脚本里调 mvn、CI 跑构建、IDE 起子进程时又会掉回错误的 JDK,同样的问题周期性复发,且更难联想到根因。

正确做法:

bash 复制代码
# ~/.zshenv
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

IDE 是完全独立的一套,不读任何 shell 配置(除非从终端启动)。IntelliJ IDEA 需要单独确认 Settings → Build Tools → Maven → Runner → JREProject Structure → Project → SDK

6.2 显式声明 annotationProcessorPaths

不要依赖"Lombok 在 compile classpath 上,javac 会自动发现它"这个隐式行为------它在 JDK 23 已经不成立了。

xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.13.0</version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.30</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

配置后插件会显式传 --processor-path。实测该选项可以重新激活注解处理,效果与 -proc:full 相同。

不做会怎样 :构建行为依赖 JDK 版本的默认策略,跨大版本升级时静默改变。较新版本的 maven-compiler-plugin 还提供了 <proc> 参数可显式设为 full,具体从哪个版本引入以你依赖的插件版本文档为准。

这条还有个额外收益:把处理器路径与编译 classpath 分开,Lombok 就不会被当成运行时依赖误传递下去。

6.3 升级过老的 maven-compiler-plugin

本次事故中的插件版本是 2.3.2,发布于 2011 年。构建日志里有一条容易被忽略的告警:

text 复制代码
[WARNING] Parameter 'parameters' is unknown for plugin 'maven-compiler-plugin:2.3.2:compile'

意思是父 pom 里配置的 <parameters>true</parameters> 根本没有生效 ------这个参数让 javac 保留方法参数名(-parameters),Spring 的构造器注入、不写 value@PathVariable、以及部分 JSON 反序列化都依赖它。

不做会怎样:一个配了但不生效的参数,比没配更危险------你以为有,实际没有,问题会在运行期以"参数名变成 arg0"的形式爆出来。

遇到 Parameter 'X' is unknown for plugin 这类告警要当错误看,它说明你的配置和插件版本对不上。

6.4 用 --release 替代 source / target

顺带一提本次排查中发现的另一个隐患。项目用的是:

xml 复制代码
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>

-target 8 只保证字节码版本 是 52,但编译时链接的仍是当前构建 JDK 的类库。用 JDK 26 编译出 target 8 的字节码,你可以调用到 Java 8 里不存在或签名不同的 API。

经典案例是 ByteBuffer.flip():Java 8 里它继承自 Buffer,返回 Buffer;Java 9 加了协变返回类型,返回 ByteBuffer。用高版本 JDK 编译、低版本 JDK 运行,就会在运行期抛 NoSuchMethodError

xml 复制代码
<!-- 推荐 -->
<maven.compiler.release>8</maven.compiler.release>

--release 8 会使用 JDK 内置的 Java 8 API 签名做校验,编译期就能发现这类问题。

不做会怎样 :本地编译全绿,部署到目标运行时报 NoSuchMethodError,且报错位置与真实原因无关。

6.5 CI 与本地锁同一个 JDK

.mvn/jvm.config.sdkmanrc、CI 配置里的 setup-java,以及 Maven Toolchains,任选一种把构建 JDK 固定下来并纳入版本控制。

不做会怎样:本地能过 CI 挂、或者反过来,排查成本翻倍。本次事故本质上就是"本地 JDK 被包管理器悄悄改了",而这件事在任何一台开发机上都可能独立发生。

6.6 一条经验判断

看到满屏 找不到符号: 方法 getXxx() / 构造器参数长度不同 / 方法引用无效,且这些符号都由注解处理器生成时,先跑 mvn -v 看构建 JDK,不要读代码。

这三类报错的组合是注解处理器整体失效的特征信号。同样的判断也适用于 MapStruct、Dagger、AutoValue 等其他基于注解处理的框架------它们失效时同样是"生成物集体消失"。

7. 总结

三条可迁移的判断:

  1. 报错数量与问题数量无关。 上百个错误可能只有一个根因。先找报错之间的共性,再决定读哪段代码------本次的共性是"缺失的符号全部由 Lombok 生成",找到这一点后问题就从几十个类收敛到了一个环境变量。

  2. java -versionmvn -v 是两个独立的值。 前者走 PATH,后者走 JAVA_HOME,包管理器可能在你不知情时改变后者。排查构建问题时以 mvn -v 为准。

  3. 失效模式为"少做一件事"的组件,可观测性天然低下。 Lombok 反射 javac 内部(合理)+ JDK 23 关闭默认注解处理(合理)叠加,产生了一个连报错都没有的失效。设计任何"默认启用、可被关闭"的机制时,关闭路径都应该留下痕迹;而依赖"过渡期告警"的迁移方案,对跨版本跳跃的用户是失效的。

留一个开放问题:Lombok 这种"用反射突破规范限制换取能力"的模式,在 JDK 持续收紧内部访问(sun.misc.Unsafe 已标记为终将移除,从上面的告警里就能看到)的趋势下还能走多久?如果它某天走不下去,你的代码库里有多少 @Data 需要还原成手写代码?

8. 引用

相关推荐
纪伊路上盛名在12 分钟前
了解一点Docker
java·docker·容器
码农阿豪15 分钟前
Seedance 2.0/2.5 虚拟素材能跨 Key 共用吗?一次讲清 Asset ID、账号隔离与 SaaS 素材架构
java·运维·架构
Meta3920 分钟前
Java八股文之Spring Boot中解决Redis和MySQL的数据一致性问题
java·spring boot·redis
坐吃山猪1 小时前
IDEA调试中Evaluate常用操作
java·python·intellij-idea
吴长建先生1 小时前
ASP.NET Core WebAPI 服务在 IM 即时
java
MuMuMu12231 小时前
文旅户外场景智能回收终端工程难点解析:越华环保集团碳惠小屋耐候与供电系统实践
java·大数据·算法
偷偷写博客1 小时前
Gitee → GitHub 自动同步配置手册
java
西峰u1 小时前
Java IO 完整学习笔记|字节流、字符流、缓冲流、资源关闭全梳理
java·笔记·学习
L-岁月染过的梦1 小时前
把常用开发小工具收进 IDEA:Develop Helper 插件介绍
java·数据库·intellij-idea