RouterFunction

You have to know the functional programming is so hot in these days. Spring introduced functional style in the 5.x era. Utilize with the new RouterFunction, you can handle your web requests in a series of fluent APIs instead of writing a @RestController.

For example, there is a simple controller class.

@RestController
@RequestMapping
class MessageController {

    @GetMapping
    Flux<Message> allMessages(){
        return Flux.just(
            Message.builder().body("hello Spring 5").build(),
            Message.builder().body("hello Spring Boot 2").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.fromObject(
                         Arrays.asList(
                             Message.builder().body("hello Spring 5").build(),
                             Message.builder().body("hello Spring Boot 2").build()
                         )
                     )
                 )
                );
}

The reoute(from RouterFunctions) accepts a RequestPredicate and HandlerFunction. HandlerFunction is a @FunctionalInteface.

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 RouterFunction is ported back to Servlet stack since 5.2, check my example hantsy/spring-webmvc-functional-sample.