RouterFunction¶
Functional routing provides a concise, fluent alternative to @RestController. Introduced in the Spring 5 era, RouterFunction remains fully supported in Spring Framework 7. RouterFunction lets you compose routes and handlers using a functional API instead of controller classes.
For example, there is a simple controller class.
@RestController
@RequestMapping
class MessageController {
@GetMapping
Flux<Message> allMessages(){
return Flux.just(
Message.builder().body("hello Spring Framework 7").build(),
Message.builder().body("hello Spring Boot 4").build()
);
}
}
You can write the same functionality with a RouterFunction bean instead.
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/"),(ServerRequest req)-> ok()
.body(
BodyInserters.fromValue(
Arrays.asList(
Message.builder().body("hello Spring Framework 7").build(),
Message.builder().body("hello Spring Boot 4").build()
)
)
)
);
}
The route (from RouterFunctions) accepts a RequestPredicate and a HandlerFunction. HandlerFunction is a @FunctionalInterface.
RouterFunctions and RequestPredicates are helpers to make it easy to assemble the request routes and predicates.
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
Mono<T> handle(ServerRequest var1);
}
For the complete codes, check spring-reactive-sample/boot-start and spring-reactive-sample/boot-start-routes.
You can extract the handler codes into a new class.
The following code fragments demonstrates the same features of PostController but written in RouterFunction.
@Bean
public RouterFunction<ServerResponse> routes(PostHandler postController) {
return route(GET("/posts"), postController::all)
.andRoute(POST("/posts"), postController::create)
.andRoute(GET("/posts/{id}"), postController::get)
.andRoute(PUT("/posts/{id}"), postController::update)
.andRoute(DELETE("/posts/{id}"), postController::delete);
}
The implementation of HandlerFunction are centralized in a PostHandler class.
@Component
class PostHandler {
private final PostRepository posts;
public PostHandler(PostRepository posts) {
this.posts = posts;
}
public Mono<ServerResponse> all(ServerRequest req) {
return ServerResponse.ok().body(this.posts.findAll(), Post.class);
}
public Mono<ServerResponse> create(ServerRequest req) {
return req.bodyToMono(Post.class)
.flatMap(post -> this.posts.save(post))
.flatMap(p -> ServerResponse.created(URI.create("/posts/" + p.getId())).build());
}
public Mono<ServerResponse> get(ServerRequest req) {
return this.posts.findById(req.pathVariable("id"))
.flatMap(post -> ServerResponse.ok().body(Mono.just(post), Post.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> update(ServerRequest req) {
return Mono
.zip(
(data) -> {
Post p = (Post) data[0];
Post p2 = (Post) data[1];
p.setTitle(p2.getTitle());
p.setContent(p2.getContent());
return p;
},
this.posts.findById(req.pathVariable("id")),
req.bodyToMono(Post.class)
)
.cast(Post.class)
.flatMap(post -> this.posts.save(post))
.flatMap(post -> ServerResponse.noContent().build());
}
public Mono<ServerResponse> delete(ServerRequest req) {
return ServerResponse.noContent().build(this.posts.deleteById(req.pathVariable("id")));
}
}
For the complete codes, check spring-reactive-sample/boot-routes.
NOTE: The
RouterFunctionis ported back to Servlet stack since 5.2, check my example hantsy/spring-webmvc-functional-sample.