函数式端点 (Functional Endpoints)
Spring Web MVC 包含 WebMvc.fn,这是一个轻量级的函数式编程模型。在该模型中,函数被用来路由和处理请求,且契约设计为不可变的。它是基于注解编程模型的替代方案,但同样运行在 DispatcherServlet 之上。
概览 (Overview)
在 WebMvc.fn 中,HTTP 请求由 HandlerFunction 处理:该函数接收 ServerRequest 并返回 ServerResponse。 路由则由 RouterFunction 完成:该函数接收 ServerRequest 并返回一个可选的 HandlerFunction。
快速示例
java
// 定义路由
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
// 定义处理器
public class PersonHandler {
public ServerResponse getPerson(ServerRequest request) {
String id = request.pathVariable("id");
Person person = repository.find(id);
return ok().body(person);
}
}kotlin
// 使用 Kotlin DSL 定义路由
val route = router {
accept(APPLICATION_JSON).nest {
GET("/person/{id}", handler::getPerson)
GET("/person", handler::listPeople)
}
POST("/person", handler::createPerson)
}核心接口
1. ServerRequest
可以通过 body() 方法读取请求体,通过 pathVariable() 获取路径变量,或者通过 bind() 方法将参数绑定到对象。
2. ServerResponse
不可变接口,提供流式 Builder 来构建响应,例如设置状态码、Headers 或 Body。支持异步结果(如 CompletableFuture 或 Publisher)作为 Body。
3. RouterFunction
用于路由。通常使用 RouterFunctions.route() 提供的 Fluent Builder 或 Kotlin DSL 来创建。支持嵌套路由(Nested Routes),可以提取公共的路径前缀或 Predicate 条件。
过滤器 (Filtering)
你可以通过 before、after 或 filter 方法为路由添加逻辑(类似于注解模型中的拦截器或过滤器)。
补充教学
1. 为什么选择函数式模型?
- 无副作用:所有的请求和响应都是不可变的,便于测试和并发处理。
- 显式而非隐式:在注解模型中,请求是如何映射到方法的由 Spring 内部黑盒处理;在函数式模型中,你可以清晰地看到路由匹配的顺序(路由是按定义顺序计算的)。
- 启动速度:相比于扫描成百上千个注解,直接执行 lambda 函数的启动开销更小。
2. 精位理解:路由匹配顺序
注意:函数式路由是按顺序匹配的。 如果你定义了:
GET("/{id}", ...)GET("/all", ...)请求/all永远会被第一个路由捕获(因为"all"被当作了id)。在注解模型中,Spring 会自动选择“最精确”的匹配,但在 WebMvc.fn 中,你必须手动将具体的路由放在通用的路由之前。
3. 代码组织建议
不要在 RouterFunction 内部写过长的 Lambda 逻辑。推荐遵循以下模式:
- Handler 类:负责业务逻辑处理,输入
ServerRequest,输出ServerResponse。 - Routes 类:只负责将 URL 路径映射到 Handler 的方法引用上。
- Configuration 类:将
RouterFunction注册为 Spring Bean。