以下这段内容是 Spring Framework 官方文档中关于 WebMvc.fn 功能性端点(Functional Endpoints)的完整介绍,它属于 Spring Web MVC 模块的一部分,但采用的是 函数式编程模型(functional programming model),而不是我们常见的基于注解(如 @Controller、@RequestMapping)的命令式风格。
下面我将用通俗易懂的方式,系统地帮你 理解这段内容的核心思想和关键概念,并解释它在实际开发中的意义。
一、整体理解:什么是 Functional Endpoints?
✅ 简单一句话:
Spring WebMvc.fn 是 Spring MVC 提供的一种函数式编程方式来定义 Web 接口(REST API),它是对传统
@Controller注解方式的替代方案。
对比传统方式(注解式)
| 特性 | 注解式(@Controller) | 函数式(WebMvc.fn) |
|---|---|---|
| 编程模型 | 命令式、面向对象 | 函数式、面向函数 |
| 路由方式 | @RequestMapping("/xxx") | RouterFunction 函数定义路由 |
| 请求处理 | 方法体作为处理器 | HandlerFunction 函数作为处理器 |
| 是否可变 | 可变对象(类字段) | 强调不可变性(ServerRequest, ServerResponse) |
| 配置方式 | 类 + 方法 + 注解 | Java/Kotlin 函数 + DSL 构建器 |
| 更适合 | 大多数项目 | 高度模块化、轻量级、函数式偏好者 |
二、核心组件详解
1. HandlerFunction<T>:请求处理器函数
相当于
@RequestMapping方法的“方法体”。
// 类型定义
interface HandlerFunction<ServerResponse> {
ServerResponse handle(ServerRequest request) throws Exception;
}
- 接收一个
ServerRequest(封装了请求信息) - 返回一个
ServerResponse(封装响应结果) - 是一个函数接口,可以用 Lambda 表达式实现
✅ 示例:
HandlerFunction<ServerResponse> helloWorld = request ->
ServerResponse.ok().body("Hello World");
这相当于写了一个 REST 接口返回 “Hello World”
2. ServerRequest:不可变的请求对象
替代了 HttpServletRequest,但更现代化(JDK 8+ 风格),提供了便捷方法:
| 方法 | 说明 |
|---|---|
request.pathVariable("id") | 获取路径参数 /user/123 中的 123 |
request.queryParam("name") | 获取查询参数 ?name=Tom |
request.params() | 所有查询参数 |
request.body(Person.class) | 解析请求体为对象(JSON → Java 对象) |
request.headers() | 请求头 |
✅ 示例:
public ServerResponse getPerson(ServerRequest request) {
int id = Integer.parseInt(request.pathVariable("id"));
Person person = repository.findById(id);
return person != null ?
ServerResponse.ok().body(person) :
ServerResponse.notFound().build();
}
3. ServerResponse:不可变的响应对象
使用 构建器模式(Builder Pattern) 创建响应,强调“不可变”,链式调用。
| 方法 | 说明 |
|---|---|
ServerResponse.ok() | 200 OK |
ServerResponse.created(location) | 201 Created(带 Location) |
ServerResponse.notFound() | 404 Not Found |
.contentType(MediaType.APPLICATION_JSON) | 设置 Content-Type |
.body(data) | 设置响应体(支持对象自动序列化) |
.build() | 构建最终响应 |
✅ 示例:
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(person);
4. RouterFunction<T>:路由函数(代替 @RequestMapping)
决定哪个请求由哪个
HandlerFunction处理。
@FunctionalInterface
interface RouterFunction<ServerResponse> {
Optional<HandlerFunction<ServerResponse>> route(ServerRequest request);
}
- 如果匹配成功,返回对应的
HandlerFunction - 否则返回
Optional.empty()
✅ 使用 RouterFunctions.route() 构建 DSL
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", handler::getPerson)
.GET("/person", handler::listPeople)
.POST("/person", handler::createPerson)
.build();
这相当于定义了三个接口:
GET /person/{id}→ 调用getPersonGET /person→ 调用listPeoplePOST /person→ 调用createPerson
5. RequestPredicate:请求谓词(路由条件)
用来精细化控制路由规则,比如:
- 路径匹配
- HTTP 方法
- Content-Type
- Accept 头
- 查询参数等
route()
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", contentType(APPLICATION_JSON), handler::createPerson)
.build();
上面表示:只有 Accept 头包含 JSON 的 GET 请求才匹配
/person
还可以组合:
path("/api").and(accept(APPLICATION_JSON)) // 路径 + Accept
常用组合操作:
and(...):同时满足or(...):满足其一
6. 嵌套路由(Nested Routes)——减少重复
就像 @RequestMapping("/person") 放在类上一样,可以统一前缀。
route()
.path("/person", b -> b
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople)
.POST("", handler::createPerson))
.build();
进一步优化:共享 Accept 条件
route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("", handler::createPerson))
.build();
所有嵌套内的 GET 请求都要求 Accept: application/json
7. 过滤器(Filtering)——类似拦截器或 AOP
可以在请求前后添加逻辑,比如:
- 添加 header
- 日志记录
- 认证授权
✅ 三种方式:
.before(...):前置处理.after(...):后置处理.filter(...):环绕过滤(最强大)
示例:安全检查过滤器
.route()
.path("/person", b -> b
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request); // 放行
} else {
return ServerResponse.status(UNAUTHORIZED).build(); // 拒绝
}
})
.build();
类似于 Spring Security 的作用,但这里是函数式写法
⚠️ 注意:嵌套内的 filter 不会影响外部,作用域是局部的。
8. 验证(Validation)
虽然没有直接支持 @Valid,但你可以手动调用 Spring Validator:
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
然后在 createPerson 中调用这个方法即可。
提示:也可以集成 JSR-303(Bean Validation),通过注入
LocalValidatorFactoryBean实现全局验证。
9. 如何运行?——注册为 Spring Bean
你需要把 RouterFunction 定义成 Spring Bean,框架会自动检测并注册。
@Configuration
public class WebConfig {
@Bean
public RouterFunction<?> personRoutes(PersonHandler handler) {
return route()
.path("/person", b -> b
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople)
.POST("", handler::createPerson))
.build();
}
}
Spring Boot 会自动启用以下组件:
RouterFunctionMapping:发现所有RouterFunction并合并HandlerFunctionAdapter:让DispatcherServlet能调用函数
✅ 所以你不需要手动启动服务器,它仍然运行在标准的
DispatcherServlet流程中!
三、为什么用?优缺点分析
✅ 优点
| 优点 | 说明 |
|---|---|
| 函数式风格 | 更适合函数式编程爱好者(尤其是 Kotlin 用户) |
| 高度模块化 | 路由清晰、易于组合、复用性强 |
| 无注解污染 | 控制器类不再需要 @Controller、@RequestMapping 等注解 |
| DSL 友好 | 使用 .GET(...), .POST(...) 构建路由,语义清晰 |
| 更适合测试 | HandlerFunction 是纯函数,容易单元测试 |
| Kotlin 友好 | 支持 DSL、扩展函数、高阶函数,代码更简洁 |
❌ 缺点
| 缺点 | 说明 |
|---|---|
| 学习成本高 | 需要理解函数式编程、Optional、Predicate 等概念 |
| 社区使用少 | 大多数项目仍使用 @RestController |
| IDE 支持弱 | 不如注解方式直观(比如 Ctrl+Click 跳转) |
| 文档少 | 相比注解方式,资料较少 |
不支持 @Valid 直接绑定 | 需要手动验证 |
四、Kotlin 示例更优雅
Kotlin 版本利用了 DSL 和扩展函数,代码更加简洁:
val route = router {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
}
POST("", handler::createPerson)
filter { req, next ->
if (securityManager.allow(req.path())) next(req)
else status(UNAUTHORIZED).build()
}
}
}
看起来像 Kotlin 构建 UI 的 DSL,非常清晰!
✅ 总结:一句话理解 Functional Endpoints
Spring WebMvc.fn 把 Web 接口定义从“类 + 注解 + 方法”变成了“函数 + 路由 + 构建器”的函数式风格,让你用更现代、更灵活的方式编写 REST API。
什么时候该用?
| 场景 | 推荐使用 |
|---|---|
| 新项目,团队熟悉函数式编程 | ✅ 推荐 |
| 想尝试轻量级、无注解的 Web 层 | ✅ 推荐 |
| 使用 Kotlin 开发 | ✅ 强烈推荐(DSL 很美) |
已有项目,团队习惯 @Controller | ❌ 不推荐强行改造 |
| 需要快速上手、社区支持多 | ❌ 推荐用注解方式 |
延伸阅读建议
- 官方文档:Spring WebMvc.fn
- Spring Boot + WebMvc.fn 示例 GitHub 项目
- Kotlin + Spring WebMvc.fn + Coroutines 实践
如果你告诉我你的具体需求(比如是否在用 Kotlin?是否想尝试函数式?是否有性能要求?),我可以给你更具体的建议或代码模板。
浙公网安备 33010602011771号