告别臃肿部署!Java Serverless 函数计算架构全解与实战选型指南

引言

Java 作为企业级开发的首选语言,凭借完善的生态、强类型安全保障与成熟的企业级特性,在过去二十多年里始终占据着开发语言的主流地位。但传统 Java 应用的部署模式,无论是单体应用还是微服务架构,都始终面临着服务器运维成本高、资源利用率低、弹性伸缩能力弱、发布周期长等核心痛点。

Serverless 架构的出现,为 Java 开发者打开了全新的思路。但行业内长期存在"Java 冷启动慢,不适合 Serverless"的刻板印象,让很多 Java 开发者对这一架构望而却步。


一、Serverless 核心本质与 Java 的适配逻辑

1.1 Serverless 的核心定义与核心特征

根据 CNCF(云原生计算基金会)的权威定义,Serverless 是一种构建和运行应用程序的范式,它消除了基础设施管理的复杂性,让开发者无需关注服务器的运维、扩容、容灾等底层工作,只需聚焦于业务代码本身。

Serverless 架构包含两大核心形态,二者相辅相成构成完整的 Serverless 体系:

  • FaaS(函数即服务) :以函数为最小部署和运行单元,事件驱动执行,平台负责自动弹性伸缩、资源调度与运维,是本文的核心讲解对象。
  • BaaS(后端即服务) :将后端能力以全托管服务的形式提供,开发者无需管理服务端,直接通过 API 调用即可使用,比如托管数据库、对象存储、消息队列、缓存服务等。

Serverless 的核心特征可以总结为四点:

  1. 无服务器管理:开发者完全无需关注服务器的采购、部署、运维、补丁更新等工作。
  2. 事件驱动:函数的执行由特定事件触发,比如 HTTP 请求、文件上传、消息通知、定时任务等,无事件时不占用资源。
  3. 自动弹性伸缩:平台根据事件的并发量,自动扩缩容函数实例,从0到成千上万的实例无缝扩展,无需人工干预。
  4. 按需付费:仅按照函数的实际执行时间和消耗的资源计费,无执行时不产生任何费用,大幅降低资源成本。

1.2 Java 与 Serverless 的适配优势

很多开发者对 Java 在 Serverless 场景的适配性存在误解,事实上,Java 不仅能适配 Serverless 架构,还能凭借自身的特性,在企业级 Serverless 场景中发挥独特的优势:

  • 成熟的企业级开发生态:Java 拥有 Spring 全家桶、MyBatis 等海量成熟的企业级组件,开发者可以直接复用现有技术积累,无需像 Node.js、Python 等语言一样重新构建技术体系。
  • 强类型安全保障:编译期的类型检查可以提前发现大量代码问题,减少线上故障概率,这对于企业级核心业务的 Serverless 化至关重要。
  • 完善的可观测性工具链:Java 拥有成熟的日志、监控、链路追踪、问题排查工具,能够完美适配 Serverless 场景的可观测性需求。
  • 跨平台与跨厂商适配能力:Java 一次编写、到处运行的特性,让业务代码可以无缝适配不同云厂商的 FaaS 平台与开源 Serverless 平台,避免厂商锁定。

1.3 Java 在 Serverless 场景的核心挑战与底层逻辑

Java 在 Serverless 场景的核心挑战集中在冷启动问题,这也是行业内对 Java Serverless 最大的顾虑。要解决这个问题,首先要彻底理解 Java 冷启动的底层逻辑。

冷启动,指的是当事件触发时,平台需要从零开始启动一个全新的函数实例,完成 JVM 初始化、类加载、应用初始化、业务代码执行的全流程。Java 冷启动耗时较长的核心原因,来自于 JVM 的启动流程与 Java 应用的初始化逻辑:

  1. JVM 进程启动:操作系统创建 JVM 进程,初始化 JVM 运行时环境,包括内存区域划分、GC 器初始化、系统属性加载等。
  2. 类加载流程:JVM 通过双亲委派模型,从 Bootstrap ClassLoader 到 Application ClassLoader,逐级加载应用所需的类文件,过程中需要完成字节码读取、验证、解析、初始化,每一步都有对应的时间开销。
  3. 字节码编译:JVM 默认采用解释执行模式,启动初期通过解释器执行字节码,后续通过 JIT 即时编译器将热点代码编译为机器码,这个过程会占用启动阶段的计算资源。
  4. 应用框架初始化:Spring 等企业级框架在启动时,需要完成 Bean 的扫描、实例化、依赖注入、上下文刷新等大量工作,这是 Java 应用启动耗时的主要来源之一。
  5. 业务资源初始化:数据库连接池、HTTP 客户端、缓存客户端等重资源的初始化,也会增加启动耗时。

除了冷启动之外,Java 应用的内存占用相对较高,而 FaaS 平台大多按照内存规格与执行时间计费,这也会对 Java Serverless 应用的成本产生一定影响。后续章节会针对这些挑战,提供完整的、可落地的优化方案。


二、Java Serverless 冷启动优化核心方案与底层原理

冷启动优化是 Java Serverless 落地的核心,目前行业内已经形成了一套完整的优化体系,从编译期、运行时、启动流程、JVM 参数多个维度,彻底解决 Java 冷启动的痛点。

2.1 编译期优化:GraalVM 原生镜像

GraalVM 是 Oracle 推出的高性能多语言运行时,其核心能力之一就是 AOT(提前编译),可以将 Java 字节码直接编译为对应平台的机器码,生成独立的可执行文件,也就是原生镜像(Native Image)。

底层原理

传统的 Java 应用运行在 JVM 之上,启动时需要完成 JVM 初始化、类加载、字节码解释执行等全流程。而 GraalVM AOT 编译在应用构建阶段,就完成了所有的类加载、字节码验证、静态分析,将应用所有用到的代码、依赖的类库、甚至精简后的 JVM 核心组件,全部编译为机器码,生成一个独立的可执行文件。

这个过程中,GraalVM 会通过静态分析实现代码裁剪(Tree Shaking),剔除所有未被使用的类、方法、字段,大幅减小应用的体积。运行时,无需启动完整的 JVM,无需类加载,无需解释执行,直接运行机器码,启动耗时可以从传统的数秒降低到百毫秒以内,甚至十毫秒级别。

适用场景与注意事项

GraalVM 原生镜像适合对冷启动延迟要求极高的在线业务场景,比如面向用户的 HTTP 接口。但需要注意,AOT 编译是基于静态分析的,对于 Java 的反射、动态代理、动态类加载等动态特性,需要在编译前通过配置文件明确声明,否则会出现运行时异常。目前 Spring Boot 3+、MyBatis-Plus 等主流框架已经原生支持 GraalVM 原生镜像,大幅降低了开发成本。

2.2 运行时快照优化:OpenJDK CRaC 与云厂商快照方案

CRaC(Coordinated Restore at Checkpoint)是 OpenJDK 主导的开源项目,核心是通过运行时快照技术,彻底解决 Java 冷启动问题。

底层原理

CRaC 的核心逻辑非常简单:在 Java 应用完全启动完成、所有初始化工作都执行完毕后,对整个 JVM 的运行时状态做一个完整的快照,包括堆内存、线程状态、类加载信息、文件句柄、网络连接等所有运行时数据,将快照持久化到磁盘。

当需要启动新的应用实例时,无需重新走 JVM 启动、类加载、应用初始化的全流程,直接从快照中恢复 JVM 的运行时状态,瞬间完成实例启动。整个恢复过程的耗时可以控制在 10 毫秒以内,完全消除了冷启动的性能损耗。

云厂商的落地实现

目前主流云厂商的 FaaS 平台,已经基于快照技术推出了对应的冷启动优化方案,开发者无需修改代码,直接开启即可使用:

  • AWS Lambda SnapStart:基于 CRaC 实现,在函数发布版本时,自动对函数应用做快照,触发函数时直接从快照恢复,冷启动耗时降低 90% 以上。
  • 阿里云函数计算 FC 镜像快照:针对容器镜像部署的 Java 应用,提供镜像快照加速能力,启动耗时降低 80% 以上。
  • 腾讯云 SCF 预置并发快照:针对预置并发实例,提供快照恢复能力,实现毫秒级实例启动。

2.3 类加载与启动流程优化:AppCDS 与懒加载

对于无法使用原生镜像或快照方案的场景,也可以通过优化类加载与启动流程,大幅降低冷启动耗时。

AppCDS 应用类数据共享

AppCDS(Application Class Data Sharing)是 JDK 内置的类加载优化技术,核心是将应用的类加载数据缓存起来,实现跨 JVM 实例的共享复用。

传统的类加载流程中,每个 JVM 实例都需要单独完成类的读取、验证、解析,生成对应的类元数据。而 AppCDS 可以在应用首次启动时,将所有核心类的元数据缓存到共享归档文件中,后续启动的 JVM 实例,直接读取归档文件中的类元数据,无需重新执行类加载的全流程,类加载耗时可以降低 50% 以上。

懒加载优化

懒加载的核心逻辑,是将非启动必需的初始化工作,从应用启动阶段推迟到实际使用时执行,减少启动阶段的时间开销。

最常用的懒加载优化,是开启 Spring Boot 的全局懒加载,只需在配置文件中添加 spring.main.lazy-initialization=true,即可让所有 Bean 的实例化与依赖注入,推迟到第一次被访问时执行,而非启动时一次性完成。对于 Spring Cloud Function 应用,还可以开启函数级别的懒加载,只有对应事件触发时,才初始化对应的函数 Bean,大幅减少启动时的初始化工作量。

2.4 JVM 运行时参数优化

针对 Serverless 场景的特点,调整 JVM 运行时参数,也可以有效降低冷启动耗时,优化运行时性能:

  1. 分层编译优化 :Serverless 函数大多执行时间较短,JIT 的 C2 编译器还没完成热点代码编译,函数就已经执行完成。可以通过 -XX:TieredStopAtLevel=1 参数,关闭 C2 编译器,只使用 C1 编译器,减少启动阶段的编译开销,启动速度提升 30% 以上。
  2. 堆内存优化 :将 -Xmx-Xms 设置为相同的值,避免 JVM 运行时动态调整堆内存大小,减少 GC 开销与内存抖动。同时根据函数的实际内存需求,合理设置堆内存大小,避免内存浪费。
  3. GC 器选择 :推荐使用 ZGC 低延迟垃圾回收器,通过 -XX:+UseZGC 参数开启。ZGC 的最大停顿时间不超过 10 毫秒,不会因为 GC 停顿导致函数执行超时,非常适合 Serverless 场景。
  4. 类验证优化 :通过 -Xverify:none 参数关闭字节码验证,减少类加载阶段的时间开销。需要注意,该参数仅适用于已经经过测试的可信应用,避免安全风险。

三、Java 函数计算核心架构设计

3.1 函数计算的通用架构模型

Java 函数计算应用的通用架构,遵循事件驱动的核心逻辑,整体分为事件源、函数计算平台、函数执行环境、业务函数、BaaS 服务五大核心组件,架构图如下:

各组件的核心职责如下:

  • 事件源:函数执行的触发方,常见的事件源包括 HTTP 请求、对象存储文件事件、消息队列消息事件、定时任务、数据库变更事件、物联网设备上报事件等。
  • 函数计算平台:FaaS 平台的核心控制面,负责事件路由、实例调度、自动弹性伸缩、日志监控采集、计费统计、安全管控等核心能力。
  • 函数执行环境:函数运行的底层环境,包括操作系统、JDK 运行时、系统依赖、应用配置等,平台会为每个函数实例提供隔离的执行环境。
  • 业务函数:开发者编写的 Java 业务代码,是架构的核心,遵循单一职责原则,每个函数只处理一类事件,完成一个特定的业务逻辑。
  • BaaS 服务:函数依赖的全托管后端服务,函数本身不保存任何状态,所有状态数据都存储在 BaaS 服务中,实现函数的无状态设计。

3.2 事件驱动的函数架构设计

Serverless 架构的核心是事件驱动,函数的执行完全由事件触发,而非主动轮询或持续运行。Java 函数的事件驱动架构设计,需要遵循以下核心原则:

  1. 事件与函数一一对应:一种类型的事件,对应一个专门的处理函数,避免一个函数处理多种类型的事件,导致代码臃肿、职责不清晰。
  2. 事件标准化:定义统一的事件格式,包括事件元数据、事件体、事件来源、事件时间等核心字段,确保事件的可追溯性、可扩展性。
  3. 同步与异步分离:面向用户的同步请求(如 HTTP 接口),使用同步函数处理,保证响应延迟;非实时的异步任务(如文件处理、消息通知),使用异步函数处理,提高系统吞吐量。
  4. 事件链路可观测:为每个事件生成唯一的链路 ID,贯穿事件的全生命周期,包括触发、函数执行、下游调用、结果返回,实现全链路的日志、监控、追踪。

事件驱动架构的核心优势,是实现了业务逻辑的完全解耦,每个函数都是独立的、可独立部署、独立扩缩容的单元,系统的可扩展性、可维护性大幅提升。同时,事件驱动架构天然适配 Serverless 的按需付费、自动弹性伸缩的特性,资源利用率最大化。

3.3 函数拆分的核心原则

函数拆分是 Java Serverless 架构设计的核心环节,拆分粒度过粗,会导致函数代码臃肿、冷启动慢、职责不清晰;拆分粒度过细,会导致函数数量爆炸、调用链复杂、运维成本高、链路延迟增加。

Java 函数拆分需要遵循以下四大核心原则:

  1. 单一职责原则:一个函数只负责一件事,只处理一个业务场景、一种事件类型。比如用户查询和用户创建,需要拆分为两个独立的函数;文件上传和文件解析,也需要拆分为两个独立的函数。
  2. 变更频率一致原则:将变更频率相同的业务逻辑,放在同一个函数中;变更频率不同的逻辑,拆分为不同的函数。比如核心的支付逻辑,和运营相关的统计逻辑,变更频率差异极大,需要拆分为不同的函数,避免频繁发布核心支付函数。
  3. 数据局部性原则:处理同一个数据实体的业务逻辑,优先放在同一个函数中,减少跨函数的数据传输、远程调用,降低链路延迟,减少出错概率。
  4. 性能边界原则:执行时间、性能要求差异极大的逻辑,必须拆分为不同的函数。比如面向用户的毫秒级 HTTP 接口,和分钟级的大数据批量处理逻辑,性能要求完全不同,必须拆分,避免批量处理逻辑影响在线接口的响应延迟。

3.4 函数的内部代码分层架构

虽然函数是最小的部署单元,代码量相对较小,但依然需要清晰的代码分层,避免代码混乱、可维护性差。Java 函数的内部代码,推荐采用四层架构设计,从外到内依次为:

  1. 函数入口层:函数的对外入口,负责接收事件、事件格式转换、入参校验、异常统一处理,调用业务逻辑层,不包含任何业务逻辑。
  2. 业务逻辑层:核心业务逻辑的实现层,负责业务规则处理、事务控制、流程编排,调用数据访问层与外部服务,是函数的核心。
  3. 数据访问层:负责与数据库、缓存、对象存储等 BaaS 服务的交互,封装数据读写的逻辑,向上层提供统一的数据访问接口。
  4. 通用工具层:封装通用的工具方法、常量定义、异常类型、日志规范等,被其他层复用,避免代码重复。

这种分层架构,完全符合 Java 企业级开发的规范,代码的可维护性、可测试性、可复用性都有保障,同时也符合 Serverless 函数的轻量化特性。


四、主流 Java Serverless 平台选型全解

Java Serverless 的落地,首先要选择合适的运行平台。目前主流的平台分为两大类:商用云厂商 FaaS 平台、开源 Serverless 平台,不同的平台有不同的特性、适用场景,开发者需要根据自身的业务需求,选择最合适的平台。

4.1 商用云厂商 FaaS 平台选型对比

商用云厂商的 FaaS 平台,是目前企业级 Java Serverless 应用的首选,平台提供了完整的托管能力、丰富的事件源集成、完善的可观测性工具,开发者无需关注底层基础设施,只需聚焦于业务代码开发。

主流商用 FaaS 平台的核心特性对比如下:

平台名称 Java 支持度 冷启动优化能力 Spring 生态适配 事件源丰富度 核心优势 适用场景
AWS Lambda 非常完善,官方深度支持 SnapStart 快照、GraalVM 原生镜像、预置并发 官方适配 Spring Cloud Function 极丰富,覆盖 AWS 全生态服务 生态最成熟,全球化部署能力强,稳定性高 出海业务、全球化部署、AWS 生态用户
阿里云函数计算 FC 非常完善,国内支持度最高 镜像快照、预留实例、弹性实例、GraalVM 原生镜像 深度适配 Spring Boot、Spring Cloud 丰富,覆盖阿里云全生态服务 国内市场份额最高,文档完善,本地化服务好 国内业务、阿里云生态用户、企业级核心业务
腾讯云 SCF 完善 预置并发、镜像加速、GraalVM 原生镜像 适配 Spring Boot、Spring Cloud 丰富,覆盖腾讯云全生态,深度集成微信生态 微信生态集成能力强,使用门槛低 微信生态业务、小程序后端、腾讯云生态用户
华为云 FunctionGraph 完善 预留实例、快照加速、CRaC 支持 适配 Spring Boot、Spring Cloud 丰富,覆盖华为云全生态 政企支持能力强,安全合规能力完善 政企客户、华为云生态用户、国产化业务

各平台核心特性详解

  1. AWS Lambda AWS Lambda 是全球最早的 FaaS 平台,也是行业的标杆,Java 支持非常成熟,是目前 Java Serverless 生态最完善的平台。其推出的 SnapStart 功能,基于 OpenJDK CRaC 实现,无需修改代码,即可将 Java 函数的冷启动耗时降低 90% 以上。同时,Lambda 深度适配 Spring Cloud Function,业务代码可以无缝迁移,无需绑定平台特性。Lambda 最大的优势是全球化部署能力,覆盖全球主流区域,适合出海业务。
  2. 阿里云函数计算 FC 阿里云 FC 是国内市场份额最高的 FaaS 平台,对 Java 的支持非常完善,提供了从冷启动优化、弹性伸缩、可观测性到生态集成的全链路能力。FC 提供了预留实例、弹性实例、镜像快照等多种冷启动优化方案,针对 Java 应用做了深度优化,同时深度集成了阿里云的 RDS、OSS、RocketMQ 等全生态服务,是国内企业级 Java 应用 Serverless 化的首选平台。
  3. 腾讯云 SCF 腾讯云 SCF 的核心优势是与微信生态的深度集成,小程序、公众号、企业微信的事件,可以直接触发 SCF 函数,是微信生态业务 Serverless 化的最佳选择。SCF 对 Java 的支持完善,提供了预置并发、镜像加速等冷启动优化能力,同时适配 Spring Boot 等主流框架,使用门槛低,适合中小型业务与微信生态场景。
  4. 华为云 FunctionGraph 华为云 FunctionGraph 的核心优势是国产化支持与政企服务能力,符合国内的安全合规要求,对 Java 的支持完善,提供了预留实例、快照加速等优化能力,深度集成华为云的全生态服务,适合政企客户、国产化业务场景。

4.2 开源 Serverless 平台选型

对于有私有化部署需求、不想被云厂商锁定、需要跨云部署的企业,开源 Serverless 平台是最佳选择。目前主流的开源 Serverless 平台如下:

  1. Knative Knative 是 CNCF 孵化的开源 Serverless 平台,基于 Kubernetes 构建,已经成为业界开源 Serverless 的事实标准。Knative 提供了两大核心能力: Serving(服务治理),提供自动弹性伸缩(包括缩容到0)、灰度发布、流量管理等能力;Eventing(事件驱动),提供统一的事件模型、事件路由、事件订阅能力。Knative 完美支持 Java 应用,Spring Boot、Spring Cloud 应用可以无缝部署到 Knative 上,无需修改代码。适合已经拥有 Kubernetes 集群、需要私有化部署、跨云部署的企业。
  2. OpenFunction OpenFunction 是 CNCF 沙箱项目,由 KubeSphere 团队发起的开源函数计算平台,兼容 Knative、Dapr、KEDA 等云原生标准组件,提供了完整的 FaaS 能力,支持 Java、Go、Python 等多种语言。OpenFunction 原生支持事件驱动架构,提供了丰富的事件源集成,同时支持函数的构建、发布、运行全生命周期管理,使用门槛比 Knative 更低,适合开源爱好者、中小企业私有化部署场景。
  3. Apache OpenWhisk Apache OpenWhisk 是 Apache 顶级开源项目,成熟稳定的开源 FaaS 平台,支持 Java 等多种语言,提供了完整的函数生命周期管理、事件驱动、弹性伸缩能力。OpenWhisk 已经被 IBM Cloud、腾讯云等多家厂商采用作为底层内核,稳定性有保障,适合企业级私有化部署场景。

4.3 平台选型核心决策因素

Java Serverless 平台的选型,需要结合自身的业务需求,综合考虑以下六大核心因素:

  1. 业务部署区域:业务主要部署在国内还是海外,优先选择对应区域覆盖完善的云厂商,降低网络延迟。
  2. 现有技术生态:已经在使用哪个云厂商的基础设施服务,优先选择同一个厂商的 FaaS 平台,大幅降低生态集成成本、网络成本。
  3. 冷启动性能要求:对延迟敏感的在线业务,优先选择提供快照优化、预留实例能力的平台;对延迟不敏感的异步任务,对冷启动优化能力的要求可以降低。
  4. 私有化部署需求:需要私有化部署、跨云部署的场景,优先选择 Knative、OpenFunction 等开源平台;无私有化需求的,优先选择商用云厂商平台,降低运维成本。
  5. 成本预算:对比不同平台的计费模式,结合业务的请求量、执行时间、内存规格,计算整体成本,选择性价比最高的平台。
  6. 技术栈适配:优先选择对 Spring 生态、GraalVM、CRaC 等技术适配完善的平台,降低开发、优化的成本。

五、Java Serverless 实战开发

本章节将通过完整的项目,讲解 Java Serverless 应用的开发流程、代码规范、部署方式,所有代码均基于 JDK 17 编写,适配主流 FaaS 平台。

5.1 项目基础环境与依赖配置

项目采用 Maven 进行依赖管理,基于 Spring Boot 3.x、Spring Cloud Function 开发,Spring Cloud Function 是 Spring 官方推出的函数式编程框架,完美适配 Serverless 架构,实现了业务代码与 FaaS 平台的解耦,一套代码可以无缝部署到阿里云 FC、AWS Lambda 等多个平台。

完整的 pom.xml 配置如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>serverless-java-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>serverless-java-demo</name>
    <description>Java Serverless Demo Project</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.0.37</mysql.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <guava.version>33.1.0-jre</guava.version>
        <springdoc.version>3.0.0</springdoc.version>
        <lombok.version>1.18.32</lombok.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>${guava.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.2 数据库表结构设计

项目采用 MySQL 8.0 作为数据库,使用 MyBatis-Plus 作为持久层框架,用户表的建表语句如下:

sql 复制代码
CREATE TABLE `t_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(64) NOT NULL COMMENT '用户名',
  `email` varchar(128) NOT NULL COMMENT '邮箱',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除 0-未删除 1-已删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  UNIQUE KEY `uk_email` (`email`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

5.3 核心代码实现

通用响应类

统一的接口响应格式,包名 com.jam.demo.common

kotlin 复制代码
package com.jam.demo.common;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 通用响应类
 *
 * @author ken
 * @since 2026-03-24
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "通用响应结果")
public class Result<T> {

    @Schema(description = "响应码", example = "200")
    private Integer code;

    @Schema(description = "响应消息", example = "操作成功")
    private String message;

    @Schema(description = "响应数据")
    private T data;

    /**
     * 成功响应
     *
     * @param data 响应数据
     * @param <T>  数据类型
     * @return 响应结果
     */
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }

    /**
     * 失败响应
     *
     * @param code    响应码
     * @param message 错误消息
     * @param <T>     数据类型
     * @return 响应结果
     */
    public static <T> Result<T> fail(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

用户实体类

对应数据库表结构,包名 com.jam.demo.entity

less 复制代码
package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类
 *
 * @author ken
 * @since 2026-03-24
 */
@Data
@TableName("t_user")
@Schema(description = "用户实体")
public class User {

    @Schema(description = "用户ID", example = "1")
    @TableId(type = IdType.AUTO)
    private Long id;

    @Schema(description = "用户名", example = "jam_demo")
    @TableField("username")
    private String username;

    @Schema(description = "邮箱", example = "jam@demo.com")
    @TableField("email")
    private String email;

    @Schema(description = "手机号", example = "13800138000")
    @TableField("phone")
    private String phone;

    @Schema(description = "创建时间")
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @Schema(description = "更新时间")
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @Schema(description = "是否删除")
    @TableLogic
    @TableField("is_deleted")
    private Integer isDeleted;
}

用户 Mapper 接口

基于 MyBatis-Plus 实现,包名 com.jam.demo.mapper

java 复制代码
package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户Mapper接口
 *
 * @author ken
 * @since 2026-03-24
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

MyBatis-Plus 配置类

配置分页插件与字段自动填充,包名 com.jam.demo.config

kotlin 复制代码
package com.jam.demo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;

/**
 * MyBatis-Plus配置类
 *
 * @author ken
 * @since 2026-03-24
 */
@Configuration
public class MybatisPlusConfig implements MetaObjectHandler {

    /**
     * 配置MyBatis-Plus拦截器
     *
     * @return 拦截器实例
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

OpenAPI 配置类

Swagger3 接口文档配置,包名 com.jam.demo.config

kotlin 复制代码
package com.jam.demo.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * OpenAPI配置类
 *
 * @author ken
 * @since 2026-03-24
 */
@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI openAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("Java Serverless Demo API")
                        .description("Java Serverless 函数计算示例接口文档")
                        .version("1.0.0"));
    }
}

应用配置文件

application.yml 配置,放在 resources 目录下:

yaml 复制代码
spring:
  application:
    name: serverless-java-demo
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/serverless_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
  cloud:
    function:
      definition: getUserById;createUser;handleFileUploadEvent
  main:
    lazy-initialization: true
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      logic-delete-field: isDeleted
      logic-delete-value: 1
      logic-not-delete-value: 0
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true
    path: /swagger-ui.html

5.4 业务函数实现

Spring Cloud Function 提供了三个核心函数式接口,对应不同的业务场景:

  • Function<T, R>:接收一个输入参数,返回一个结果,对应同步请求场景,如 HTTP 接口。
  • Consumer<T>:接收一个输入参数,无返回值,对应异步事件处理场景,如消息消费、文件事件处理。
  • Supplier<T>:无输入参数,返回一个结果,对应定时任务、数据拉取场景。

HTTP 接口函数:根据 ID 查询用户

实现 Function<Long, Result<User>> 接口,包名 com.jam.demo.function

kotlin 复制代码
package com.jam.demo.function;

import com.jam.demo.common.Result;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.function.Function;

/**
 * 根据ID查询用户函数
 *
 * @author ken
 * @since 2026-03-24
 */
@Slf4j
@Component("getUserById")
public class GetUserByIdFunction implements Function<Long, Result<User>> {

    private final UserMapper userMapper;

    public GetUserByIdFunction(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 函数执行方法
     *
     * @param userId 用户ID
     * @return 用户查询结果
     */
    @Override
    public Result<User> apply(Long userId) {
        log.info("开始查询用户,用户ID:{}", userId);
        if (ObjectUtils.isEmpty(userId)) {
            log.warn("用户ID为空");
            return Result.fail(400, "用户ID不能为空");
        }
        User user = userMapper.selectById(userId);
        if (ObjectUtils.isEmpty(user)) {
            log.warn("用户不存在,用户ID:{}", userId);
            return Result.fail(404, "用户不存在");
        }
        log.info("用户查询成功,用户ID:{}", userId);
        return Result.success(user);
    }
}

HTTP 接口函数:创建用户

实现 Function<User, Result<Long>> 接口,使用编程式事务控制,包名 com.jam.demo.function

kotlin 复制代码
package com.jam.demo.function;

import com.jam.demo.common.Result;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.function.Function;

/**
 * 创建用户函数
 *
 * @author ken
 * @since 2026-03-24
 */
@Slf4j
@Component("createUser")
public class CreateUserFunction implements Function<User, Result<Long>> {

    private final UserMapper userMapper;
    private final TransactionTemplate transactionTemplate;

    public CreateUserFunction(UserMapper userMapper, TransactionTemplate transactionTemplate) {
        this.userMapper = userMapper;
        this.transactionTemplate = transactionTemplate;
    }

    /**
     * 函数执行方法
     *
     * @param user 用户信息
     * @return 创建结果,包含用户ID
     */
    @Override
    public Result<Long> apply(User user) {
        log.info("开始创建用户,用户名:{}", user.getUsername());
        if (!StringUtils.hasText(user.getUsername())) {
            log.warn("用户名不能为空");
            return Result.fail(400, "用户名不能为空");
        }
        if (!StringUtils.hasText(user.getEmail())) {
            log.warn("邮箱不能为空");
            return Result.fail(400, "邮箱不能为空");
        }
        Long userId = transactionTemplate.execute(new TransactionCallback<Long>() {
            @Override
            public Long doInTransaction(TransactionStatus status) {
                try {
                    userMapper.insert(user);
                    return user.getId();
                } catch (Exception e) {
                    status.setRollbackOnly();
                    log.error("创建用户失败,用户名:{}", user.getUsername(), e);
                    return null;
                }
            }
        });
        if (ObjectUtils.isEmpty(userId)) {
            return Result.fail(500, "创建用户失败");
        }
        log.info("用户创建成功,用户ID:{}", userId);
        return Result.success(userId);
    }
}

事件处理函数:处理文件上传事件

实现 Consumer<String> 接口,处理对象存储的文件上传事件,包名 com.jam.demo.function

typescript 复制代码
package com.jam.demo.function;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.function.Consumer;

/**
 * 处理文件上传事件函数
 *
 * @author ken
 * @since 2026-03-24
 */
@Slf4j
@Component("handleFileUploadEvent")
public class HandleFileUploadEventFunction implements Consumer<String> {

    /**
     * 事件处理方法
     *
     * @param eventJson 事件JSON字符串
     */
    @Override
    public void accept(String eventJson) {
        log.info("接收到文件上传事件,事件内容:{}", eventJson);
        if (!StringUtils.hasText(eventJson)) {
            log.warn("事件内容为空");
            return;
        }
        JSONObject eventObject = JSON.parseObject(eventJson);
        String bucketName = eventObject.getString("bucketName");
        String fileName = eventObject.getString("fileName");
        String fileSize = eventObject.getString("fileSize");
        if (!StringUtils.hasText(bucketName) || !StringUtils.hasText(fileName)) {
            log.warn("事件参数不完整,bucketName:{},fileName:{}", bucketName, fileName);
            return;
        }
        log.info("开始处理文件,bucket:{},文件名:{},文件大小:{}", bucketName, fileName, fileSize);
        // 此处可扩展文件解析、缩略图生成、数据入库等业务逻辑
        log.info("文件处理完成,bucket:{},文件名:{}", bucketName, fileName);
    }
}

项目启动类

包名 com.jam.demo

kotlin 复制代码
package com.jam.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 项目启动类
 *
 * @author ken
 * @since 2026-03-24
 */
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
public class ServerlessJavaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerlessJavaDemoApplication.class, args);
    }
}

5.5 函数调用与部署

本地调用方式

Spring Cloud Function 会自动将注册的函数,暴露为 HTTP 接口,本地启动项目后,可通过以下方式调用:

  • 查询用户:GET http://127.0.0.1:8080/getUserById?userId=1
  • 创建用户:POST http://127.0.0.1:8080/createUser,请求体为用户信息 JSON
  • 处理文件事件:POST http://127.0.0.1:8080/handleFileUploadEvent,请求体为事件 JSON

跨平台部署

Spring Cloud Function 提供了针对不同 FaaS 平台的适配适配器,只需在 pom.xml 中添加对应平台的适配器依赖,无需修改业务代码,即可将项目部署到对应的平台:

  • 部署到 AWS Lambda:添加 spring-cloud-function-adapter-aws 依赖,打包为 ZIP 包上传即可。
  • 部署到阿里云 FC:添加 spring-cloud-function-adapter-aliyun-fc 依赖,通过镜像或 JAR 包部署即可。
  • 部署到腾讯云 SCF:添加 spring-cloud-function-adapter-tencent-scf 依赖,通过 JAR 包部署即可。

5.6 GraalVM 原生镜像优化实战

针对冷启动要求极高的场景,可通过 GraalVM 编译为原生镜像,大幅降低启动耗时。具体实现步骤如下:

  1. 添加反射配置文件 :在 resources/META-INF/native-image 目录下,创建 reflect-config.json 文件,配置反射相关的类信息:
json 复制代码
[
  {
    "name": "com.jam.demo.entity.User",
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  },
  {
    "name": "com.jam.demo.mapper.UserMapper",
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  }
]
  1. 执行编译命令 :安装 GraalVM JDK 17 后,在项目根目录执行 mvn native:compile 命令,完成原生镜像编译。 3. 运行镜像 :编译完成后,在 target 目录下会生成独立的可执行文件,直接运行即可,启动耗时可降低至 100ms 以内。

六、Java Serverless 最佳实践与避坑指南

6.1 函数开发最佳实践

  • 无状态设计:函数实例是动态创建和销毁的,绝对不能在函数中通过静态变量、实例变量保存业务状态,所有状态必须存储在数据库、缓存等外部 BaaS 服务中。
  • 资源复用:数据库连接池、HTTP 客户端、配置加载等重资源的初始化,必须放在函数初始化阶段执行,而非每次函数执行时创建,减少执行时间开销。
  • 幂等性设计:FaaS 平台大多提供至少一次的投递保证,函数可能会被重复调用,所有函数必须实现幂等性处理,避免重复消费、数据重复写入等问题。
  • 完善的异常处理:函数必须捕获所有的业务异常与系统异常,打印完整的异常日志,返回清晰的错误信息,避免异常抛出导致函数执行失败、事件重试。
  • 严格的入参校验:所有输入参数必须做严格的合法性校验,避免非法参数导致的业务异常、安全漏洞。

6.2 冷启动优化最佳实践

  • 精简依赖:定期清理 pom.xml 中未使用的依赖,避免依赖膨胀,减少类加载的数量与启动耗时。
  • 开启懒加载:开启 Spring Boot 全局懒加载,减少启动阶段的 Bean 实例化数量,降低启动耗时。
  • 优先使用平台快照能力:云厂商提供的快照优化方案,无需修改代码,即可大幅降低冷启动耗时,是冷启动优化的首选方案。
  • 合理使用预留实例:对延迟要求极高的核心业务,使用预留实例,彻底消除冷启动问题;对低频调用的业务,使用按需实例,降低成本。
  • 控制函数代码体量:避免单个函数包含过多的业务逻辑,减少函数的代码量与依赖,降低启动耗时。

6.3 成本优化最佳实践

  • 合理设置内存规格:FaaS 平台的 CPU 性能与内存规格成正比,并非内存越大越好,需要通过压测,找到性能与成本的最佳平衡点。
  • 优化函数执行时间:通过代码优化、资源复用、减少 IO 操作等方式,降低函数的单次执行时间,减少计费时长。
  • 合并低频调用函数:将多个调用频率极低的函数,合并为一个函数,减少冷启动的次数,降低冷启动带来的性能损耗与成本开销。
  • 合理设置超时时间:根据函数的实际执行时间,设置合理的超时时间,避免异常函数长时间执行,导致不必要的成本开销。
  • 合理使用预留实例:对流量稳定的业务,使用预留实例,相比按需实例,成本可降低 40% 以上。

6.4 安全最佳实践

  • 最小权限原则:为函数分配最小的权限,仅授予业务必需的资源访问权限,避免权限过大导致的安全风险。
  • 敏感信息加密存储:数据库密码、AK/SK 等敏感信息,绝对不能硬编码在代码或配置文件中,必须使用云厂商的密钥管理服务存储与获取。
  • 严格的输入校验:所有外部输入必须做严格的校验与过滤,避免 SQL 注入、XSS、命令注入等安全漏洞。
  • 网络隔离:将函数部署在私有网络中,仅通过 API 网关对外暴露服务,避免公网直接访问函数实例。
  • 完善的日志审计:打印完整的函数执行日志,包括请求参数、执行结果、异常信息、操作人等,便于安全审计与问题排查。

6.5 常见避坑指南

  1. 静态变量状态混乱:函数实例可能会被平台复用,同一个实例会处理多个请求,使用静态变量保存请求级别的状态,会导致不同请求之间的数据混乱。
  2. 数据库连接数暴涨:每次函数执行都创建新的数据库连接,会导致数据库连接数暴涨,数据库性能下降,甚至宕机,必须使用连接池,在初始化阶段创建连接。
  3. 函数执行超时:未合理设置函数超时时间,或者函数中存在耗时过长的操作,导致函数执行超时,业务失败。
  4. 依赖膨胀:引入了大量未使用的依赖,导致函数包体过大,冷启动耗时大幅增加。
  5. 未处理重试导致数据异常:未实现函数的幂等性,平台的重试机制会导致数据重复写入、业务重复执行,引发数据异常。

七、Java Serverless 未来发展趋势

随着 JDK 技术的不断发展与云原生生态的完善,Java 在 Serverless 场景的适配性会越来越强,未来的发展趋势主要集中在以下几个方向:

7.1 虚拟线程的全面普及

JDK 21 已经正式发布了虚拟线程(Virtual Thread)特性,彻底解决了 Java 传统平台线程的开销问题,大幅提高了 Java 应用的并发处理能力。对于 Serverless 场景的 IO 密集型业务,虚拟线程可以在极小的内存开销下,支持成千上万的并发请求,大幅提高函数的资源利用率,降低成本。未来,虚拟线程会成为 Java Serverless 应用的标配。

7.2 冷启动优化技术的标准化

CRaC、AppCDS 等冷启动优化技术,会逐步成为 OpenJDK 的标准特性,JDK 会原生支持运行时快照、类数据共享等能力,无需额外的配置与适配,即可大幅降低 Java 应用的启动耗时。同时,主流云厂商的 FaaS 平台,会原生支持这些标准特性,Java 冷启动的问题会被彻底解决。

7.3 GraalVM 原生镜像生态的完善

目前 Spring Boot 3+、MyBatis-Plus 等主流框架已经原生支持 GraalVM 原生镜像,未来会有更多的 Java 开源组件原生支持 GraalVM AOT 编译,原生镜像的开发成本会大幅降低,开发者无需额外的配置,即可将 Java 应用编译为原生镜像,享受毫秒级的启动速度。GraalVM 原生镜像会成为 Java Serverless 在线业务的主流部署方式。

7.4 开源 Serverless 平台的广泛应用

随着 Knative、OpenFunction 等开源 Serverless 平台的不断成熟,企业会越来越多地采用开源 Serverless 平台,实现私有化部署、跨云部署,避免厂商锁定。Java 作为企业级开发的主流语言,会成为开源 Serverless 平台的核心支持语言,生态会越来越完善。

7.5 AI 与 Serverless 的深度融合

AI 大模型应用的开发,天然适合 Serverless 架构,按需付费、自动弹性伸缩的特性,完美匹配 AI 应用的流量波动特点。Java 成熟的企业级生态、强类型安全保障、完善的工具链,会在 AI 应用的开发中发挥重要作用,Java Serverless 会成为 AI 应用后端开发的主流选择。


结尾

Serverless 不是 Java 的敌人,而是 Java 开发者突破传统部署模式枷锁的全新机遇。长期以来,行业内对 Java 与 Serverless 的误解,本质上是对 Java 冷启动问题的刻板印象。而随着 JDK 技术的不断发展,GraalVM、CRaC 等优化技术的不断成熟,Java 冷启动的痛点已经被彻底解决,Java 在企业级开发中的生态优势、安全优势、稳定性优势,会在 Serverless 场景中得到充分的发挥。

对于 Java 开发者而言,Serverless 不是需要颠覆现有技术体系的全新技术,而是现有技术体系的延伸与升级。我们可以复用多年积累的 Java 开发经验、Spring 生态技术栈,只需遵循 Serverless 的架构设计原则,即可快速实现业务的 Serverless 化,享受 Serverless 带来的低运维成本、高资源利用率、极致弹性伸缩的优势。

相关推荐
殷紫川1 小时前
吃透云原生可观测:Metrics、Logging、Tracing 架构底层逻辑与实战全指南
云原生·架构
孟陬2 小时前
为什么国外技术大神都爱自己搭博客,而国内程序员却挤在微信公众号或掘金?
java·typescript·前端框架
GawynKing2 小时前
Java文件传输利器:MultipartFile介绍
java·开发语言
Java.熵减码农2 小时前
经典20道Java面试题系列(一)
java·开发语言
yhole2 小时前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
sthnyph2 小时前
Spring Boot 集成 Kettle
java·spring boot·后端
小飞大王6662 小时前
从零手写 React:深度解析 Fiber 架构与 Hooks 实现
前端·react.js·架构
sxhcwgcy2 小时前
Spring.factories
java·数据库·spring
arvin_xiaoting2 小时前
OpenClaw学习总结_I_核心架构_9:Multi-Agent详解
网络·学习·架构·系统架构·ai agent·multi-agent·openclaw