以下这段内容是 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} → 调用 getPerson
  • GET /person → 调用 listPeople
  • POST /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?是否想尝试函数式?是否有性能要求?),我可以给你更具体的建议或代码模板。