第 14 章:从 Spring 到 Axum/Actix-web——构建你的第一个 Rust Web 服务

作为资深的 Java 开发者,我们闭着眼睛都能用 Spring Boot 搭建起一个 RESTful API。@RestController@GetMapping@Autowired…这些注解如同魔法咒语,Spring 的自动配置和依赖注入为我们处理了大量底层细节,让我们能以惊人的速度进行开发。

现在,欢迎来到 Rust 的 Web 开发世界。这里没有“魔法”,没有运行时反射,也没有庞大的依赖注入容器。Rust 的哲学是明确、显式和编译期安全。你可能会失去一些 Spring Boot 的“开箱即用”的便利,但你将换来的是无与伦比的性能、极致的资源效率和坚如磐石的可靠性。

在本章,我们将介绍构建 Rust Web 服务的三巨头,并搭建一个简单的 API:

  • 运行时(Runtime)- Tokio: 它是 Rust 异步生态的事实标准。可以把它想象成驱动 Spring WebFlux 的 Netty 事件循环,或者是嵌入式 Tomcat 底层的线程池和 I/O 管理器。
  • Web 框架 - Axum: 一个现代、模块化的 Web 框架,与 Tokio 无缝集成。它就是 Rust 世界的 Spring Web MVC / WebFlux,负责路由、请求处理、中间件等。
  • 序列化/反序列化 - Serde: Rust 的“Jackson”。它是一个极其强大和高效的库,用于将 Rust 的数据结构与各种数据格式(如 JSON)进行相互转换。

async/.await:Rust 异步编程的核心

现代 Web 服务需要处理成千上万的并发连接。阻塞每一个请求的线程是不可行的。因此,和 Spring WebFlux 一样,现代 Rust Web 开发也是基于异步非阻塞模型的。

  • async fn: 将一个函数标记为异步函数。调用它不会立即执行,而是返回一个**Future**。这非常类似 Java 中的 CompletableFuture 或 Project Reactor 中的 Mono。它代表一个“未来会完成的计算”。
  • .await: 当你需要等待一个 Future 的结果时,使用 .await。它会“暂停”当前任务的执行,让出线程去执行其他任务,直到 Future 完成后再回来继续。这避免了线程的阻塞。

Tokio,就是那个负责调度和执行成千上万个 Future 的强大引擎。


项目设置:迎接 Cargo 的高光时刻

让我们用 Cargo 创建一个新项目,并在 Cargo.toml 中添加我们的“三巨头”依赖:

[dependencies]
# 异步运行时
tokio = { version = "1", features = ["full"] }
# Web 框架
axum = "0.7"
# 序列化/反序列化库
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

注意 features 标志,这是 Cargo 的一个强大功能,它允许我们只引入一个库中我们真正需要的部分,从而减小最终编译产物的大小。


实战:构建一个简单的用户 API

我们的目标是创建一个简单的 API,可以创建用户和获取用户列表。

第一步:定义模型(Model)和 Serde

首先,定义我们的 User 数据结构。这非常像在 Java 中创建一个 POJO。

use serde::{Deserialize, Serialize};

// derive 宏是 Rust 的编译期代码生成魔法
// 它会自动为 User 实现序列化和反序列化的能力
#[derive(Serialize, Deserialize, Debug, Clone)]
struct User {
    id: u64,
    username: String,
}

#[derive(Serialize, Deserialize)] 这个注解,就是 Serde 的威力所在。它会在编译期自动为 User 生成转换为 JSON 和从 JSON 创建实例的代码。这实现了和 Java Jackson 库类似的功能,但它不是在运行时通过反射,而是在编译期完成,因此性能更高。

第二步:创建处理器(Handler)——@RestController 的替代品

在 Axum 中,处理器就是一个个返回 Future 的异步函数。

use axum::{Json, response::IntoResponse};
use std::sync::Arc;
use tokio::sync::Mutex;

// 我们的“内存数据库”
type Db = Arc<Mutex<Vec<User>>>;

// 获取所有用户 - 类似 @GetMapping("/users")
async fn get_users(db: Db) -> impl IntoResponse {
    let users = db.lock().await;
    Json(users.clone())
}

// 创建用户 - 类似 @PostMapping("/users")
async fn create_user(
    Json(payload): Json<User>, // Axum 的提取器,自动从请求体中反序列化 JSON
    db: Db,
) -> impl IntoResponse {
    let mut users = db.lock().await;
    users.push(payload);
    (http::StatusCode::CREATED, "User created")
}
  • Json<T> 是 Axum 的一个提取器(Extractor)。当它作为参数时 (Json(payload)),它会尝试将请求体(Request Body)中的 JSON 反序列化成 User 类型。如果失败,Axum 会自动返回一个 400 Bad Request 错误。当它作为返回值时,它会将你的数据结构序列化成 JSON 响应。
第三步:定义路由(Router)——@RequestMapping 的替代品

我们需要将 URL 路径和 HTTP 方法映射到我们的处理器函数上。

use axum::{routing::{get, post}, Router};

fn app_router(db: Db) -> Router {
    Router::new()
        .route("/users", get(get_users).post(create_user))
        .with_state(db) // 将数据库状态注入到处理器中
}

Router::new() 创建了一个新的路由器,.route() 方法清晰地定义了路径和方法的映射关系。

第四步:启动服务器

最后,在 main 函数中,我们使用 Tokio 来启动我们的服务。

#[tokio::main]
async fn main() {
    // 初始化我们的内存数据库
    let db = Db::default();

    // 创建路由器
    let app = app_router(db);

    // 绑定地址和端口
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("listening on {}", listener.local_addr().unwrap());

    // 启动服务
    axum::serve(listener, app).await.unwrap();
}

#[tokio::main] 宏为我们设置好了整个异步运行时。整个启动过程代码非常简短和直白。


对决:Spring Boot vs. Rust/Axum

对比维度 Spring Boot Rust + Axum 评述
开发模型 注解驱动,依赖注入,运行时“魔法” 函数式,显式组合,编译期生成 Spring 开发速度快,Rust 更明确,无隐藏逻辑。
内存占用 (JVM + Spring 上下文,通常 100MB+ 起步) 极低 (单个二进制文件,通常 < 10MB) Rust 在容器化和 Serverless 场景中优势巨大。
启动速度 较慢 (秒级) 极快 (毫秒级)
性能/延迟 良好 (JIT 预热后性能不错,但有 GC 停顿风险) 极致 (接近 C++ 的原生性能,无 GC 停顿) Rust 提供稳定、可预测的低延迟。
编译产物 .jar 文件 + 需要 JVM 才能运行 单个、无依赖的二进制可执行文件 Rust 的部署极其简单,一个文件扔上去就能跑。
生态系统 极其成熟 (Spring Data, Security, Cloud…) 快速发展中 (但需要手动组合更多库) Spring 的生态是其最大优势,提供了全方位的解决方案。

结论: Rust 舍弃了 Spring 的运行时便利性和“魔法”,换来的是在性能、资源占用和部署简易性上的绝对优势。这是一种用“明确性”换取“极致性能”的权衡。


本章小结

我们成功地使用 TokioAxumSerde 构建了一个高性能、资源占用极低的异步 Web 服务。我们看到了 Rust 如何通过显式的函数和组合,来完成 Spring Boot 中由注解和框架“魔法”所做的工作。

这证明了 Rust 不仅仅是一门系统编程语言,它同样有能力在 Web 后端这个 Java 的传统优势领域中,提供一个极具竞争力的、现代化的选择。

你已经走完了从 Java 到 Rust 的所有核心学习路径。从内存安全到无畏并发,再到今天的 Web 实战,你已经具备了用 Rust 解决实际问题的能力。

在专栏的最后一章 《未来之路——Rust 生态圈漫游指南与持续学习》 中,我们将一起眺望远方,看看 Rust 世界里还有哪些激动人心的领域等待着我们去探索。