在 Java Web 开发的世界里,我们习惯了 Spring Boot 的“全栈式”解决方案,也领略了 Vert.x 的“响应式”魅力。然而,当面对一个简单、明确、资源受限的任务时——比如构建一个内部工具的 API、一个 IoT 设备的数据上报端点、一个微小的健康检查服务或一个快速原型——这些强大的框架有时会显得“杀鸡用牛刀”。
这时,Spark Java(注意:与 Apache Spark 大数据框架同名但完全无关)便以其极致的简洁、零配置和闪电般的启动速度,成为了一个令人耳目一新的选择。它不是一个框架,而是一个微型 DSL(领域特定语言),让您能用几行代码就搭建起一个功能完备的 RESTful 服务。
本文将深入 Spark Java 的核心,从“Hello World”开始,逐步探索其路由、过滤器、模板引擎、错误处理等特性,并通过实战案例展示如何用它构建一个生产可用的小型 API 服务。最后,我们将客观分析其适用场景,帮助您判断何时该选择这位“极简主义者”。
Spark Java 的设计哲学可以用一句话概括:“提供最少量的 API,解决最常见的 Web 问题。” 它没有 IOC 容器,没有复杂的注解,没有 XML 配置。它的一切都围绕着 HTTP 方法 和 URL 路径 展开。
get, post, before, after 几个核心方法即可上手。让我们用最经典的例子感受其简洁。
1. 添加 Maven 依赖
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.4</version>
</dependency>2. 编写主类
import static spark.Spark.*;
public class HelloWorld {
public static void main(String[] args) {
// 定义一个 GET 路由
get("/hello", (req, res) -> "Hello, Spark Java!");
// 可选:设置端口,默认是 4567
port(8080);
}
}3. 运行并访问
# 编译并运行
mvn compile exec:java
# 访问
curl http://localhost:8080/hello
# 响应: Hello, Spark Java!仅需 5 行核心代码,一个 Web 服务就诞生了!这就是 Spark Java 的魅力所在。
虽然极简,但 Spark Java 提供了构建 RESTful API 所需的所有基本构件。
Spark Java 的路由系统直观且强大。
基础 HTTP 方法
get("/users", ...); // 查询
post("/users", ...); // 创建
put("/users/:id", ...); // 全量更新
patch("/users/:id", ...); // 部分更新
delete("/users/:id", ...);// 删除路径参数 (Path Parameters)
get("/users/:id", (req, res) -> {
String userId = req.params(":id"); // 获取路径中的 id
return "User ID: " + userId;
});查询参数 (Query Parameters)
get("/search", (req, res) -> {
String query = req.queryParams("q");
String page = req.queryParams("page");
return "Searching for: " + query + " on page " + page;
});请求体 (Request Body)
对于 POST/PUT 请求,可以通过 req.body() 获取原始 JSON 或表单数据,并使用 Jackson 等库进行解析。
post("/users", (req, res) -> {
// 假设请求体是 JSON
User user = new ObjectMapper().readValue(req.body(), User.class);
// ... 保存用户逻辑
res.status(201); // 设置状态码为 Created
return user.getId();
});过滤器用于在请求处理前后执行通用逻辑,如身份验证、日志记录、CORS 处理等。
before 过滤器:在路由处理前执行。
before("/api/*", (req, res) -> {
// 对所有 /api/ 开头的路径进行认证
String apiKey = req.headers("X-API-Key");
if (!isValid(apiKey)) {
halt(401, "Unauthorized"); // 立即终止请求
}
});after 过滤器:在路由处理后执行。
after("/api/*", (req, res) -> {
// 为所有 API 响应添加统一的头部
res.header("X-Powered-By", "Spark Java");
// 统一将响应转换为 JSON
if (res.body() != null && !res.body().isEmpty()) {
res.type("application/json");
res.body(new ObjectMapper().writeValueAsString(res.body()));
}
});Spark Java 提供了优雅的错误处理机制。
全局错误处理器
exception(Exception.class, (exception, request, response) -> {
log.error("Unhandled exception", exception);
response.status(500);
response.body("Internal Server Error");
});
// 处理特定 HTTP 状态码
notFound((request, response) -> {
response.type("application/json");
return "{\"error\": \"Resource not found\"}";
});让我们通过一个完整的例子,展示如何用 Spark Java 构建一个具备 CRUD 功能的 API。
spark-todo-api/
├── pom.xml
└── src/main/java/
└── com/example/
├── Main.java
├── Todo.java
└── TodoService.java1. 数据模型 (Todo.java)
public class Todo {
private String id;
private String title;
private boolean completed;
// constructors, getters, setters...
}2. 服务层 (TodoService.java)
public class TodoService {
private final Map<String, Todo> todos = new ConcurrentHashMap<>();
private final AtomicInteger counter = new AtomicInteger();
public List<Todo> getAll() {
return new ArrayList<>(todos.values());
}
public Todo create(Todo todo) {
String id = String.valueOf(counter.incrementAndGet());
todo.setId(id);
todos.put(id, todo);
return todo;
}
public Todo update(String id, Todo updated) {
if (todos.containsKey(id)) {
updated.setId(id);
todos.put(id, updated);
return updated;
}
return null;
}
public boolean delete(String id) {
return todos.remove(id) != null;
}
}3. 应用入口 (Main.java)
import static spark.Spark.*;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
private static final ObjectMapper mapper = new ObjectMapper();
private static final TodoService service = new TodoService();
public static void main(String[] args) {
port(4567);
// 全局 JSON 响应
after((req, res) -> {
if (res.body() != null) {
res.type("application/json");
res.body(mapper.writeValueAsString(res.body()));
}
});
// CORS 支持
options("/*", (request, response) -> {
String accessControlRequestHeaders = request.headers("Access-Control-Request-Headers");
if (accessControlRequestHeaders != null) {
response.header("Access-Control-Allow-Headers", accessControlRequestHeaders);
}
String accessControlRequestMethod = request.headers("Access-Control-Request-Method");
if (accessControlRequestMethod != null) {
response.header("Access-Control-Allow-Methods", accessControlRequestMethod);
}
return "OK";
});
before((request, response) -> response.header("Access-Control-Allow-Origin", "*"));
// API 路由
get("/todos", (req, res) -> service.getAll());
post("/todos", (req, res) -> {
Todo todo = mapper.readValue(req.body(), Todo.class);
Todo created = service.create(todo);
res.status(201);
return created;
});
put("/todos/:id", (req, res) -> {
String id = req.params(":id");
Todo updated = mapper.readValue(req.body(), Todo.class);
Todo result = service.update(id, updated);
if (result == null) {
res.status(404);
return "Not Found";
}
return result;
});
delete("/todos/:id", (req, res) -> {
String id = req.params(":id");
boolean deleted = service.delete(id);
if (!deleted) {
res.status(404);
return "Not Found";
}
return "";
});
}
}这个不到 100 行代码的服务,已经具备了一个完整 REST API 的所有要素,并且可以立即投入生产使用。
虽然 Spark Java 本身是同步阻塞的,但您可以轻松集成 CompletableFuture 来处理耗时操作,避免阻塞主线程。
get("/slow-operation", (req, res) -> {
res.raw().setChunked(true); // 启用分块传输
CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
Thread.sleep(5000);
return "Done!";
}).thenAccept(result -> {
try {
res.raw().getWriter().write(result);
res.raw().getWriter().close();
} catch (IOException e) {
e.printStackTrace();
}
});
return ""; // 立即返回,不阻塞
});Spark Java 可以轻松地服务于前端静态资源。
// 将 /public 目录下的文件映射到根路径
staticFiles.location("/public");
// 或者指定外部目录
staticFiles.externalLocation("/var/www");Spark Java 提供了 SparkTest 工具,方便进行集成测试。
@Test
public void testGetAllTodos() {
// 启动 Spark 在随机端口
SparkTest.test(spark -> {
// ... 定义你的路由
}, (server) -> {
// 使用给定的 server 实例进行 HTTP 调用
HttpResponse response = Unirest.get("http://localhost:" + server.port() + "/todos").asString();
assertEquals(200, response.getStatus());
});
}特性 | Spark Java | Spring Boot WebFlux |
|---|---|---|
核心目标 | 极简、快速 | 高性能、响应式、全栈 |
并发模型 | Servlet (Thread-per-Request) | Reactive (Event-Loop) |
启动时间 | 毫秒级 | 秒级 |
内存占用 | 极低 (< 50MB) | 较高 (> 100MB) |
学习曲线 | 非常平缓 | 陡峭 (需理解响应式编程) |
生态系统 | 微小 | 庞大 (Spring 生态) |
适用场景 | 小型 API、工具、原型、边缘计算 | 高并发、低延迟、复杂业务的微服务 |
结论:
Spark Java 是 Java 生态中的一股清流。它提醒我们,在追求功能强大的同时,不应忘记“简单”的价值。对于那些不需要“大炮”的任务,一把精准的“瑞士军刀”往往更为高效和优雅。
在微服务和云原生的时代,小型、专用的服务无处不在。Spark Java 正是为这类场景而生。它可能不是您技术栈中的常驻主力,但在关键时刻,它绝对是您工具箱里那把最趁手、最可靠的工具。学会在合适的时机使用它,是每一位成熟开发者的重要技能。