尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spring AI Alibaba停更别慌:Java AI工程化替代路线与RAG实践
1. 先复盘Spring AI Alibaba 是什么停更意味着什么1.1 这个框架解决的核心问题先别急着给 Java 判死刑。Spring AI Alibaba 停更这个消息传了很多天我朋友圈里已经有兄弟把简历技能清单里的“Spring AI Alibaba”删掉了。我理解这种恐慌但问题在于大部分人并不清楚这个框架到底解决了什么问题自然也就不知道停更后会造成多大影响。Java 后端调用大模型 API 其实不难难的是工程化。几个核心痛点第一各家模型服务的请求格式不统一有的走 OpenAI 兼容协议有的有自己的 Message 结构有的返回结果千奇百怪。第二Prompt 管理很麻烦。业务里的 Prompt 不只是一个字符串它需要模板、变量替换、历史上下文拼接、系统提示词隔离。第三流式输出不好做。大模型生成内容通常是 SSE 一段时间段返回一批 tokenJava 这边要么用 WebClient 解析要么自己手写 HTTP 客户端再加上线程池和超时控制代码量马上就不一样了。第四RAG 和工具调用要跟 Spring 的事务、配置、监控体系打通。Spring AI Alibaba 当初就是把这些琐碎封装成了 Spring Boot Starter 的形态。你加一个依赖配好模型服务的 key然后注入一个 ChatClient 接口像调用普通 Service 一样去调用大模型。它还把向量库、文档读取、Prompt 模板、函数调用都串了进去让 Java 团队能在一天内跑通一个带知识库的问答机器人。所以它的价值在于“降低 Java 后端接入 AI 工程化的门槛”。如果它停更了最大的风险不是“Java 不能接大模型”而是原本依赖这套封装的业务项目会遇到兼容性问题Spring Boot 升级不匹配、依赖安全漏洞没人修、新模型的新特性适配不了。这是真实的麻烦但和 Java 语言本身没关系。1.2 停更的本质是什么一个开源项目停止更新不等于项目失败也不等于所在技术方向被否定。软件生命周期里项目停更常见的原因就这么几种上游项目接口变化太快维护者追不动所在组织战略调整投入方向变了团队人手不足社区又没人愿意接棒项目本来就是为了某个阶段性目标而生目标达成后自然就没人维护了。Spring AI Alibaba 所处的赛道非常特殊模型层几乎每隔几个月就换一套玩法。上游 Spring AI 自己都还在频繁改 API更别提它下面的厂商分支。如果背后的维护团队不再持续投入这个项目很快就会落后于主项目迭代速度。从商业角度看它更像是某家云服务商为了推广自家模型平台做的一个“集成卡点”。当模型服务开始走更通用的协议或者开发者可以直接调用主项目搞定一切时这个分支的战略价值就会迅速下降。组织觉得没必要再养一个重复轮子就停掉了。你可以把这件事理解为一个高速公路服务区关停了但这不意味着整条路不能走更不意味着以后没人出远门。真正要评估的是 Java 在 AI 时代还有没有值得走的路而不是盯着一个服务区的招牌。2. Java在AI时代到底还有没有生态位2.1 语言分工的真相Python不是万能的很多人的焦虑来自一个对比Python 生态里有一堆 AI 框架跑模型、做微调、写智能体都顺手Java 有什么社交平台上的技术热帖也喜欢渲染“AI 只认 Python”搞得好多 Java 开发心里发慌。但 Python 的强项在“模型研发”不是“业务落地”。我用一个餐厅类比来解释Python 是后厨研发团队负责不停试新菜、调佐料Java 是餐厅的中央厨房和配送系统负责把研发好的菜稳定地批量生产到几十个门店。后厨可以天天推翻重来但门店出餐不能三天两头改流程。AI 应用真正上线时要面对的是用户登录鉴权、订单数据、支付回调、权限管控、操作日志、限流熔断、故障告警。这些功能 Java 后端积累了二十多年已经有非常成熟的开源组件和企业级方案。企业不会因为一个聊天机器人就专门建一套纯净 Python 服务去替代现有 Spring 系统。大多数公司的真实诉求是“在已有业务系统上长出一个 AI 能力”。用户的身份、商品数据、订单状态都在 Java 系统里模型生成的回复要落地执行必须回到原有的事务链路里。这里天然就是 Java 的主场。2.2 Java 在 AI 基础设施中的份额再往底层看Java 在 AI 基础设施里不但没有退场反而位于关键位置。模型训练之前的数据清洗、特征计算、离线批处理大量任务跑在 Spark、Flink 这类分布式框架上它们的核心实现和 API 都是 Java/Scala 主导。模型上线之后的推理服务前面往往需要一整套流量调度、排队、限流、降级、灰度发布系统这些组件也有很多是 Java 开发的。说句实在话大模型 API 只是一个接口真正让 AI 功能扛住生产流量的是周围的工程设施。如果你在公司里负责推荐系统、风控系统、实时对账系统你会发现每天处理高并发和高吞吐的核心逻辑跟模型关系不大但和 Java 的并发模型、内存模型关系很大。AI 不会把基础设施变成无人区它只是在这片土地上新增了一批需要运输的货。所以我一直觉得Java 在 AI 时代的定位不是“做模型”而是“接模型、调模型、管模型”。模型做出来需要有人把它变成稳定可靠的产品这个差事对 Java 开发者来说反而是机会因为懂业务又懂工程的人稀缺。2.3 存量系统与新AI如何对接另一个容易被忽略的点是大多数公司做不到把所有业务推到 Python 重写也不应该这么做。未来几年会出现的场景是“老系统多了一个 AI 接口”。举个例子客户在 CRM 系统里提问“上个月华东区退单率为什么升高”系统需要从数据仓库里调取订单表分析异常趋势再用自然语言组织报告。这个过程涉及的权限、数据隔离、内部审批流程通通都在 Java 后端里。Java 开发者现在要做的不是切换语言而是学会怎么在自己熟悉的系统里嵌入模型调用、检索增强、工具调用这些能力。Spring AI Alibaba 停更只是少了一个封装好的工具但你的数据库连接、分布式事务、服务注册发现、配置中心仍然健壮。框架是外套底子还在。3. 停更了也不用慌几条靠谱的替代路线3.1 回归 Spring AI 主项目如果你原本在用 Spring AI Alibaba第一步先别急着扔掉整个方向去检查一下上游的 Spring AI 主项目。Spring AI Alibaba 可以理解为主项目的一个厂商扩展分支很多能力是共通的。主项目到现在还在正常迭代并且和 Spring Boot 版本保持同步。你只需要把依赖坐标换回主项目模型配置方式稍作调整大部分常用功能还能继续使用。当然Spring AI 主项目还很年轻API 偶尔会有破坏性变更升级小版本可能编译不过。我的建议是如果项目规模不大就直接升级到最新稳定版如果项目复杂把模型调用封装在自己的 Service 层里对外不暴露框架类型这样即使底层框架调整上层业务代码不受影响。在多模块项目里尤其值得花一天时间做这层隔离。3.2 自研轻量调用层最可控的路线停更事件恰好给我们提了一个醒过度依赖某个框架不如把最核心的模型调用抽成自己的代码。 Java 标准库里的 HttpClient 在 JDK 11 之后已经很好用配合 Jackson 就能完成大模型 API 的请求、解析、流式读取。你只需要写一个 ChatClient 接口定义 chat、streamChat、embed 等几个方法然后对应实现。自己写的调用层确实要多费点功夫处理超时、重试、SSE 解析但这些代码量不大而且完全在你的掌控之中。框架停更了你的代码不会受影响模型服务商换 API 格式了你只需要改动一个实现类。等后面需要更复杂的能力时再引入现成框架也不迟。这个思路立得住因为核心诉求是模型调用而不是框架的名气。3.3 多模型网关模式如果公司里有多个 AI 项目我建议认真考虑自建一个轻量级的多模型网关。所谓网关就是对上暴露统一接口对下对接不同的模型服务。业务方只发一种格式的请求网关做鉴权、路由、重试、限流、成本统计和日志审计。这样无论哪一个模型服务或者 AI 框架停更业务代码都不需要随之一夜重写。网关不需要特别复杂一个 Spring Boot 项目就能实现。核心是配置驱动用 Map 存放模型名称到端点地址、模型型号、Key 的映射请求过来时解析出对应的路由目标调用真实模型服务并把结果原样返回。再加上简单的滑动窗口限流就可以应对大部分场景。这个模式的价值在于它把不可控的外部依赖全部挡在了一扇门后面真正实现了“框架可以死业务不陪葬”。3.4 评估替代框架时看哪几个点在选 Java AI 框架之前我一般会看四个维度第一是否持续有版本发布长时间没有发版的直接降权第二是否兼容自己项目的 Spring Boot 版本和 JDK 版本避免引入一堆冲突第三是否覆盖自己真正需要的功能如果只需要简单的聊天流式输出大而全的框架反而是负担第四是否方便自定义扩展因为模型领域变化太快框架内置能力永远追不上实际需求。最好还看一下框架的依赖树重量级框架会拖进大量传递依赖跟公司内部的组件版本冲突。轻量级框架则更透明。在这些维度下你完全可以用 Spring AI 主项目 HttpClient 自研封装搭配少量数据库操作实现业务闭环。4. 实操案例不用任何 AI 框架纯 Spring Boot 也能做 Chat RAG4.1 环境准备与项目结构这一节我们做一个可以直接跑的示例JDK 17 或 21Spring Boot 3.2 以上Maven 项目。只依赖spring-boot-starter-web、spring-boot-starter-validation和 Jackson不需要额外引入大模型 SDK。这样的好处是所有人都能看懂而且不受任何框架停更影响。项目里我们建三个核心类LlmClient负责调用模型和 embedding 接口MemoryVectorStore负责本地向量存检索ChatController提供 HTTP 接口。结构很简单但足够跑通一个带 RAG 的聊天机器人。如果你正在做的项目比这个复杂也可以在此基础上逐步增加数据库、消息队列、缓存等组件。我特意没有使用 WebFlux因为SseEmitter已经能同时支持普通流式输出和业务代码的同步逻辑。团队如果对响应式编程不熟WebFlux 反而会带来学习成本。虽然响应式在高并发下有优势但大多企业项目并发量还没到那个程度先把业务闭环做出来更重要。4.2 实现大模型 HTTP 封装先看核心调用代码。这里用 JDK 原生的HttpClient避免了额外依赖import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.Map; public class LlmClient { private final HttpClient httpClient; private final String endpoint; private final String apiKey; private final String model; private final ObjectMapper objectMapper; public LlmClient(String endpoint, String apiKey, String model) { this.endpoint endpoint; this.apiKey apiKey; this.model model; this.httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); this.objectMapper new ObjectMapper(); } public String chat(ListMapString, String messages) throws Exception { MapString, Object body Map.of( model, model, messages, messages, stream, false ); String json objectMapper.writeValueAsString(body); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(endpoint /chat/completions)) .header(Content-Type, application/json) .header(Authorization, Bearer apiKey) .timeout(Duration.ofSeconds(60)) .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); if (response.statusCode() ! 200) { throw new RuntimeException(模型接口异常 response.statusCode() - response.body()); } JsonNode root objectMapper.readTree(response.body()); return root.at(/choices/0/message/content).asText(); } }几个关键点解释一下。第一Map.of创建的 Map 是不可变的如果你需要注入 system 消息等内容建议换成LinkedHashMap组装请求体。第二超时时间要区分连接超时和请求超时上面代码只设置了请求超时连接超时通过HttpClient构造器设置。第三真实环境里模型名称、endpoint、apiKey 应该放在配置中心或环境变量里不要硬编码在类中。4.3 流式输出的实现思路流式输出比普通返回要复杂一点点但原理不复杂。模型服务通常通过 SSE 返回事件流每行以data:开头最后以data: [DONE]结束。我们只需要在 Spring Boot 中暴露一个SseEmitter然后在线程池里不断消费 HTTP 流并发送给前端。import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; public SseEmitter streamChat(ListMapString, String messages) { SseEmitter emitter new SseEmitter(120_000L); executor.submit(() - { try (InputStream inputStream openStream(messages)) { BufferedReader reader new BufferedReader( new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line; while ((line reader.readLine()) ! null) { if (line.startsWith(data:)) { String data line.substring(5).trim(); if ([DONE].equals(data)) { emitter.complete(); return; } emitter.send(data); } } emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return emitter; }注意几点。第一SseEmitter构造器里的超时时间要大于模型的最长响应时间否则客户端还没收到全部内容连接就被服务端掐断。第二发送 SSE 的线程要使用独立的线程池不要让请求进入阻塞式业务方法里否则 Tomcat 线程会全部卡住。第三流式输出的过程中前端可能会因为网络中断而断开此时emitter.send会抛异常要在 catch 里做清理避免线程池堆积无效任务。4.4 做一个最简 RAGRAG 的完整链路包括切片、向量化、检索、拼接。我们先实现一个内存版向量存储。核心数据结构是一个列表每个元素包含文档内容和对应的嵌入向量。查询时用余弦相似度计算得分返回 top-k 结果。public class MemoryVectorStore { private final Listdouble[] vectors new ArrayList(); private final ListString documents new ArrayList(); public void add(String document, double[] embedding) { documents.add(document); vectors.add(embedding); } public ListString search(String queryEmbedding, int topK) { ListScoredDocument scored new ArrayList(); for (int i 0; i vectors.size(); i) { double score cosineSimilarity(queryEmbedding, vectors.get(i)); scored.add(new ScoredDocument(score, documents.get(i))); } scored.sort((a, b) - Double.compare(b.score(), a.score())); return scored.subList(0, Math.min(topK, scored.size())) .stream().map(ScoredDocument::document).toList(); } public double cosineSimilarity(double[] a, double[] b) { double dot 0, normA 0, normB 0; for (int i 0; i a.length; i) { dot a[i] * b[i]; normA a[i] * a[i]; normB b[i] * b[i]; } return dot / (Math.sqrt(normA) * Math.sqrt(normB) 1e-10); } }为什么用余弦相似度向量高维空间里模长经常不稳定方向比大小更有意义。余弦相似度已经能处理大部分文本检索问题。如果你的文本包含大量实体需要更好的语义区分再考虑引入重排模型但在起步阶段没必要。把 RAG 接进 Chat 的流程也很简单用户问题先用 embedding 接口编码成向量去 MemoryVectorStore 里检索关联文档把结果拼到 Prompt 的上下文位置再调用聊天接口。例如ListMapString, String messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个企业客服助手只能根据给定的参考资料回答问题。)); messages.add(Map.of(role, user, content, 参考资料\n String.join(\n, relatedDocs) \n\n用户问题 question));这里的关键是上下文的长度。一次检索可能返回好几段内容如果全部塞进 Prompt模型输入 token 会迅速增长响应延迟和成本也跟着涨。我的经验是把每段参考资料控制在 300 字左右最多检索 3 段不够再补充。4.5 配置与安全注意事项示例代码里直接用new LlmClient(...)创建对象比较粗糙。实际项目中应该让 Spring 管理依赖把 endpoint、apiKey、model 放进application.yml里用ConfigurationProperties读取。提示API Key 千万不要提交到 Git 仓库。一旦泄漏别人能拿你的 Key 刷模型调用费用账单会让你欲哭无泪。本地开发可以用环境变量注入生产环境放到配置中心或安全套件里。另外模型输入的 Prompt 是有安全边界的。我们在做 RAG 时用户问题不应该直接拼到系统提示词里至少要先做一次输入清洗过滤掉明显的注入特征。比如用户说“忽略上面的指令”时要让模型知道它仍然应该遵守预设的角色约束。这个问题靠代码解决一部分靠 Prompt 设计解决另一部分。5. 常见问题与排查技巧实录我把日常踩过的坑整理成了一张速查表按现象、原因、排查思路三个维度来列基本覆盖 Java 接大模型时的常见问题。现象可能原因排查思路启动后报 ClassNotFoundExceptionSpring AI 依赖版本与 JDK/Spring Boot 版本不兼容查看依赖树统一 BOM 版本优先使用与 Spring Boot 相同版本号的 AI 依赖调用大模型接口超时配置了过短的请求超时时间模型响应慢网络不稳把 timeout 调到 60s 以上添加重试机制但限制重试次数并发高时报连接池耗尽HttpClient 连接池没配置或复用不当用 HttpClient 单例设置连接过期时间和最大连接数配合线程池限流流式输出中途断了SSE 连接被网关或服务器提前关闭客户端断连开启心跳配置合理的SseEmitter超时增加错误事件通知机制返回内容乱码或截断未按 UTF-8 解码模型输入输出 token 上限超了保证读取流时指定字符集控制 Prompt 长度查看模型最大 token 数RAG 检索结果不相关文档切片太长或太短Embedding 模型不适合当前语言和领域调整切片长度尽量让每段包含一个完整主题换双语或领域适应模型API Key 泄漏被刷配置文件入库环境变量污染立即吊销旧 Key启用 IP 白名单和额度告警Spring AI 版本升级后编译失败框架 API 还在变化破坏性变更多先查看迁移指南再封装一层自己的 Service 接口屏蔽变化除了表格里的内容还有几个实操经验值得分享。流式输出报错是最难排查的因为前端看到的是data:流问题可能出在中间网关的 buffering。如果用了 Nginx 代理必须关闭对 SSE 的缓冲否则前端拿不到实时增量数据。配置大概是proxy_buffering off;和X-Accel-Buffering: no响应头。很多团队把时间耗在后端最后发现是 Nginx 一层的问题。再有一个细节模型接口返回的 JSON 结构在不同服务商之间并不完全一致有的把内容放在message.content有的还会多包一层choices[0].delta.content。建议写一个单测用本地固定的 JSON 夹具验证解析逻辑不然上线后才发现某些场景解析直接返回 null。关于限流不要只依赖模型服务方的配额自己服务里要有一层流控。可以用简单的令牌桶实现也可以基于第三方组件做分布式限流。否则某个促销活动一搞用户疯狂点击聊天框你的上游模型账单会瞬间爆掉。6. Java开发者如何转型AI工程6.1 先搞清楚自己该学什么看到 Spring AI Alibaba 停更很多 Java 开发的第一反应是“我要不要去学 Python”我的态度很明确没必要把时间花在模型训练上除非你真的想去研究算法。Java 开发者更适合把精力放在“AI 应用工程”这一层也就是怎么用模型 API 实现业务价值。这层需要掌握的知识可以分成三块。第一块是模型使用相关概念包括 Chat Completion、Embedding、Function Calling、Token 计费、温度参数、流式协议。第二块是检索增强和智能体编排包括文档切片、向量检索、重排、工具调用、多轮会话状态管理。第三块是工程稳定性包括超时、重试、限流、监控告警、成本控制、安全审计。这三块都和 Spring Boot 工程实践密不可分钻研它们就是 Java 开发者的独特优势。6.2 一个可执行的 60 天路线如果你已经会 Spring Boot我建议按这样的节奏来前两周用 HttpClient 接一个大模型对话 API实现普通返回和流式输出把参数封装、错误处理、SSE 解析都跑通。第三到第五周实现一个本地 RAG手写内存向量检索把一份产品手册变成一个问答机器人。第六到第八周做函数调用让模型可以调用你自己的服务方法比如查订单、查库存。第九到第十周做一个简单的多模型网关把多个模型服务统一接入加上限流、日志和成本统计最后部署到测试环境。这套路线没有依赖任何特定的 AI 框架哪怕 Spring AI Alibaba 真停了你照样能把知识迁移到新框架上。框架只是工具流程和原理才是底子。6.3 心态上避开的几个坑第一不要一上来就啃框架源码。源码是重要但那是后面为了排查问题才做的事。先跑通业务闭环你对框架设计的理解会加速。第二不要盲目追求最新版本。AI 框架迭代很快但生产环境稳定更重要新版本经常伴随破坏性变更。第三不要把 Python 和 Java 对立起来。很多团队的 AI 能力是混合的Python 做模型服务和数据处理Java 做业务编排和接口层两者分工合作很正常。还有一点很现实企业招人看的不是你会不会某个框架而是你能不能把模型能力落到业务里。会用 Spring AI Alibaba 的简历已经不被加分了能讲清楚“我在现有系统里集成了 RAG让售后工单处理效率提升了一倍”的履历才值钱。最后分享一点个人体会。项目停了不可怕怕的是你把自己绑定在一个具体框架的名字上。业内有个共识做编程的人最后拼的都是基础能力和解决问题的能力。Java 在 AI 时代还有没有希望答案取决于你愿不愿意把时间花在“怎么用模型解决问题”而不是“追着框架更新跑”。就算这个框架明天就没了只要你把 HTTP、流式、检索、工程稳定性这些基本功打牢换个轮子上路并不难。说到底语言和框架都是工具能持续交付价值的人到什么时候都有希望。
RELATED

相关推荐

free-claude-code实战:免费AI编程助手与开源本地模型混合部署指南

free-claude-code实战:免费AI编程助手与开源本地模型混合部署指南

1. 项目整体思路拆解:free-claude-code到底是什么最近“free-claude-code”这个词在开发者圈子里讨论度不低,但很多人第一眼看到它,理解的其实是两件完全不一样的事。有人以为是指“绕过付费墙去白嫖某个AI编程助手”,也有人以为是…

📅 2026/10/16 4:46:38
文件转Markdown:提升AI理解效率的底层预处理方法

文件转Markdown:提升AI理解效率的底层预处理方法

1. 为什么“直接丢文件给AI”正在悄悄拖垮你的效率你有没有过这种体验:把一份20页的PDF产品说明书拖进对话框,等了半分钟,AI只吐出一句“我已阅读该文档”,然后你问“第三章讲了什么”,它开始含糊其辞,甚至…

📅 2026/10/16 4:41:38
Linux下单例式线程池的实现与踩坑指南

Linux下单例式线程池的实现与踩坑指南

单例式线程池这个东西,听起来像是把两个经典设计凑在一起,但真正在 Linux 上落地的时候,你才会发现它远比“单例模式 线程池”的简单叠加复杂得多。我最早接触它是在某跨平台系统的后台服务模块里,当时需要在整个进程内共享唯一的…

📅 2026/10/16 4:41:38
MORE NEWS

更多资讯

📰

A*寻路算法从原理到实战:启发函数、性能调优与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

嵌入式实时噪声估计:三层递进式鲁棒算法设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

恭介的法则:研发团队如何将踩坑经验固化为可执行规则

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Colab K80 深度学习实战:环境配置、训练优化与断点续训技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Java基础类库通关指南:Object、包装类、String与Random核心考点解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Java TreeSet核心原理与实战:有序集合的去重、排序与踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬