1. Introduction

The last article gave a brief introduction to functional interface programming for Spring MVC, and let many students who did not know this new operation. There are also reactions that don’t look as good as the traditional way of writing it, but it’s the same for everyone. But we still have to dare to try new things. Java Lambada was also teased by many people when it just came out. Now I have seen it in many projects. Moving on, this article is a continuation of the previous article, and we continue to learn and use Functional Endpoint. Paradigm shifts were covered in the last article, but once you’re introduced to this approach for the first time, you often face new problems.

2. New problems

There are also some new problems with using this style. In the following sections, we will address these questions step by step with examples.

2.1 How to Handle Exceptions

Interface exception handling is required. After changing to functional style, exceptions can be handled like this:

    /** * The interface comes with exception handling logic@param userService the user service
     * @return the user by name with error handle
     */
    public RouterFunction<ServerResponse> withErrorHandle(a) {
        return RouterFunctions.route()
                .GET("/userwitherrorhandle/{username}",
                        request -> ServerResponse.ok()
                          .body(userService.getByName(request.pathVariable("username"))))
                // Exception handling
                .onError(RuntimeException.class,
                        (e, request) -> EntityResponse.fromObject(e.getMessage())
                                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                                .build())
                .build();
    }
Copy the code

You can use the onError method above and its overloaded methods for exception handling of the interface. But traditional methods have uniform exception handling! Don’t worry, we will also handle unified exceptions later.

2.2 How to Use a Filter

I have quite a bit of Spring MVC using filters, how to write filters in this style, last article missed a functional interface to handle filters HandlerFilterFunction. We use this interface to filter requests:

    /** * Specifies a filter for a particular interface. **@param userService the user service
     * @return the router function
     */
    public RouterFunction<ServerResponse> withFilter(a) {
        return RouterFunctions.route().POST("/save",
                request -> ServerResponse.ok()
                        .body(userService.saveUser(request.body(UserInfo.class))))
                // A filter logic argument is executed that carries the save release or return a bad Request with a message
                .filter((request, next) -> request.param("save").isPresent() ?
                        next.handle(request) :
                        ServerResponse.status(HttpStatus.BAD_REQUEST).body("no save"))
                .build();
    }
Copy the code

The filter method enables logging, security policies, and cross-domain functions.

2.3 How to Use Interceptors

The functional programming style does not provide the Spring MVC interceptor API, but does provide a filter pre/post processing mechanism to achieve the same effect.

    public RouterFunction<ServerResponse> getUserByName(a) {
        return RouterFunctions.route()
                .GET("/user/{username}",
                        request -> ServerResponse.ok()
                         .body(userService.getByName(request.pathVariable("username"))))
                // The path is printed
                .before(serverRequest -> {
                    log.info(serverRequest.path());
                    return serverRequest;
                })
                If the response status is 200, print response OK
                .after(((serverRequest, serverResponse) -> {
                    if (serverResponse.statusCode() == HttpStatus.OK) {
                        log.info("response ok");
                    }
                    return serverResponse;
                })).build();
    }
Copy the code

When you request /user/{username}, the before and after methods will be pre – and post-processed, respectively.

2.4 How to Perform unified Processing

Traditionally, each Controller deals with a single domain. UserController deals with User, and we give it a common prefix /v1/ User; OrderController handles the order-related business and adds a uniform prefix identifier /v1/ Order to it. Aggregation of the same business interfaces is more maintainable using functional programming we can achieve this in the following ways:

@Bean
RouterFunction<ServerResponse> userEndpoints(UserController userController) {
    return RouterFunctions.route()
            .path("/v2/user", builder -> builder
                    // /get/{username} -> /v2/user//get/{username}
                    .add(userController.getUserByName()
                            // /del/{username} -> /v2/user//del/{username}
                            .and(userController.delUser()
                                    // /save -> /v2/user/save
                                    .and(userController.saveUser()
                                            // /update -> /v2/user/update
                                            .and(userController.updateUser())))))
            .build();
}
Copy the code

You can also use routerfunctions.route ().nest. Grouping and aggregating these routes can unify filters, interceptors, and exception handling. For example, the unified exception problem mentioned in the previous article:

@Bean
RouterFunction<ServerResponse> nestEndpoints(UserController userController) {
    return RouterFunctions.route().nest(RequestPredicates.path("/v1/user"),
            builder -> builder
                    .add(userController.getUserByName())
                    .add(userController.delUser())
                    .add(userController.saveUser())
                    .add(userController.updateUser()))
            // Perform unified exception handling for the above routes
            .onError(RuntimeException.class,
                    (throwable, serverRequest) -> ServerResponse
                            .status(HttpStatus.BAD_REQUEST)
                            .body("bad req"))
            .build();
}
Copy the code

3. Summary

This article mainly on Spring MVC functional development and traditional development of equivalent features (filters, interceptors, group aggregation, etc.) for a simple description, more suitable for practical application. Functional style development is more flexible, but also a little uncomfortable for developers who are used to imperative programming, but it is becoming more and more common. So -/ if you want to be engaged in programming development for a long time, still need to master. The demo of this article can be obtained by following the public account: Felordcn in reply to mvcfun.

Follow our public id: Felordcn for more information

Personal blog: https://felord.cn