为什么需要函数式路由
注解路由很直观,但有两个局限:
- 路由定义分散在每个 controller 类中,集中查看全部路由需要扫描多个文件
- 运行时组合困难——无法根据配置动态添加/删除路由
函数式路由把所有路由定义集中在一个(或几个)RouterFunction bean 中,纯 Java 代码表达路由逻辑。
最小函数式路由
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
@Configuration
public class OrderRouter {
@Bean
public RouterFunction<ServerResponse> orderRoutes() {
return RouterFunctions.route()
.GET("/api/orders", request ->
ServerResponse.ok()
.bodyValue("orders list"))
.GET("/api/orders/{id}", request -> {
String id = request.pathVariable("id");
return ServerResponse.ok()
.bodyValue("order " + id);
})
.POST("/api/orders", request ->
ServerResponse.ok()
.bodyValue("created"))
.build();
}
}
RouterFunctions.route() 是可变构建器,链式添加路由规则。.build() 返回不可变的 RouterFunction。
RequestPredicates 匹配
除了简单路径匹配,可以用 RequestPredicates 做复杂条件:
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Bean
public RouterFunction<ServerResponse> advancedRoutes() {
return RouterFunctions.route()
// 路径 + 方法
.nest(path("/api/users"), builder -> builder
.GET("/{id}", this::getUser)
.POST("", this::createUser)
)
// 请求头匹配
// header 缺失时 h.header(...) 返回 null,常量在前避免 NPE
.route(headers(h -> "v2".equals(h.header("X-Api-Version"))), this::v2Handler)
// 查询参数匹配
.route(queryParam("format", "csv"), this::csvHandler)
// Content-Type 匹配
.route(contentType(MediaType.APPLICATION_JSON), this::jsonHandler)
.build();
}
Mono<ServerResponse> getUser(ServerRequest req) {
String id = req.pathVariable("id");
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(new User(Long.parseLong(id), "user-" + id));
}
Mono<ServerResponse> createUser(ServerRequest req) {
return req.bodyToMono(User.class)
.flatMap(user -> ServerResponse.ok()
.bodyValue("Created: " + user.name()));
}
请求体读取
函数式路由中从 ServerRequest 读取请求体:
// 读取为 Mono
Mono<User> userMono = request.bodyToMono(User.class);
// 读取为 Flux
Flux<User> userFlux = request.bodyToFlux(User.class);
// 读取为字符串
Mono<String> body = request.bodyToMono(String.class);
// 读取原始 DataBuffer(流式处理大文件)
Flux<DataBuffer> buffers = request.body(BodyExtractors.toDataBuffers());
响应构建
ServerResponse 提供丰富的响应构建 API:
// 200 OK + JSON
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(object);
// 201 Created + Location 头
ServerResponse.created(URI.create("/api/orders/123"))
.bodyValue(createdOrder);
// 204 No Content
ServerResponse.noContent().build();
// 404 Not Found
ServerResponse.notFound().build();
// 400 Bad Request
ServerResponse.badRequest()
.bodyValue(Map.of("error", "Invalid input"));
// 503 Service Unavailable
ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE)
.bodyValue(Map.of("retry_after", 30));
嵌套路由
nest() 可以给一组路由加统一前缀:
@Bean
public RouterFunction<ServerResponse> apiRoutes() {
return RouterFunctions.route()
.nest(path("/api/v1"), v1 -> v1
.nest(path("/users"), users -> users
.GET("", this::listUsers)
.GET("/{id}", this::getUser)
.POST("", this::createUser)
)
.nest(path("/orders"), orders -> orders
.GET("", this::listOrders)
.GET("/{id}", this::getOrder)
)
)
.build();
}
这种结构在大型 API 中比分散的注解 controller 更易维护——路由层级一目了然。
函数式过滤器
函数式路由也可以加横切逻辑:
@Bean
public RouterFunction<ServerResponse> filteredRoutes() {
RouterFunction<ServerResponse> routes = RouterFunctions.route()
.GET("/api/protected", this::protectedHandler)
.build();
return routes.filter((request, next) -> {
long start = System.nanoTime();
System.out.println("→ " + request.method() + " " + request.path());
return next.handle(request)
.doFinally(sig -> {
long ms = (System.nanoTime() - start) / 1_000_000;
System.out.println("← " + request.path() + " in " + ms + "ms");
});
});
}
.filter() 的参数是 (ServerRequest, HandlerFunction) -> Mono<ServerResponse>,与第 1.2 节的中间件模式完全一致。
注解路由 vs 函数式路由
| 维度 | 注解路由 | 函数式路由 |
|---|---|---|
| 代码组织 | 分散在多个 @Controller 类 | 集中在 RouterFunction bean |
| 路由查看 | 需扫描注解 | 路由表在代码中显式可见 |
| 动态路由 | 困难 | 可在运行时组装 |
| IDE 重构 | 字符串路径易遗漏 | 路径常量可安全重构 |
| 团队熟悉度 | 高(Spring MVC 用户熟悉) | 需额外学习成本 |
| 与 Spring 生态 | AOP、@Valid 等开箱即用 | 部分功能需手动配置 |
eventd v2 的 API 层使用混合策略:
- 主要业务接口(用户、订单、事件)用注解路由——开发效率高
- 网关/internal 接口(健康检查、metrics、动态配置)用函数式路由——便于集中管理
从函数式路由调用 service
@Service
public class OrderService {
public Mono<Order> findById(Long id) { ... }
public Flux<Order> findAll() { ... }
public Mono<Order> create(Order order) { ... }
}
@Component
public class OrderHandler {
private final OrderService service;
public OrderHandler(OrderService service) {
this.service = service;
}
public Mono<ServerResponse> list(ServerRequest req) {
return ServerResponse.ok()
.body(service.findAll(), Order.class);
}
public Mono<ServerResponse> get(ServerRequest req) {
Long id = Long.parseLong(req.pathVariable("id"));
return service.findById(id)
.flatMap(order -> ServerResponse.ok().bodyValue(order))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
Handler 类(纯函数)+ Service 类(业务逻辑)的分层与注解路由的 Controller + Service 分层等价。
小结
| 能力 | API | 说明 |
|---|---|---|
| 定义路由 | RouterFunctions.route().GET(...).POST(...).build() | 链式构建 |
| 路径匹配 | path("/api/...") | nest() 加前缀 |
| 条件匹配 | queryParam()、headers()、contentType() | 复杂路由条件 |
| 读取请求体 | request.bodyToMono() / bodyToFlux() | 与 @RequestBody 等价 |
| 构建响应 | ServerResponse.ok() / notFound() / badRequest() | 流式 API |
| 过滤器 | .filter((req, next) -> ...) | 与中间件模式一致 |
下一节 4.3 WebFlux 过滤器 :WebFilter、HandlerFilterFunction、顺序控制、与函数式路由 filter 的对比。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。