Spring MVC advanced functional programming

1. Preface

Last right Functional interface programming of Spring MVC A simple introduction, so that many students do not know the new operation. There is also a reaction that this kind of writing seems to be less pleasing than the traditional writing method, but in fact, everyone is the same. But we should dare to try new things. Java Lambada just came out and make complaints about it. Now I see it in many projects. Well, back to the main topic, this article is an extension of the previous one. We will continue to understand and use Functional Endpoint. Paradigm shift has been introduced in the last article, but once you first touch this way, you will often face new problems.

2. New problems

We also encounter some new problems when using this style. Next, we will solve these problems step by step through examples.

2.1 how to handle exceptions

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

    /**
     * Interface with exception handling logic
     *
     * @param userService the user service
     * @return the user by name with error handle
     */
    public RouterFunction<ServerResponse> withErrorHandle() {
        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();
    }

You can use the onError method above and its overload method to handle interface exceptions. But the traditional method has unified exception handling! Don't worry. We will also handle the unified exception later.

2.2 how to use the filter

I still have a lot of Spring MVC using filters. How to write filters in this style? In the previous article, a functional interface HandlerFilterFunction that processes filters was missed. We use this interface to filter requests:

    /**
     * Specify a filter for a specific interface
     *
     * @param userService the user service
     * @return the router function
     */
    public RouterFunction<ServerResponse> withFilter() {
        return RouterFunctions.route().POST("/save",
                request -> ServerResponse.ok()
                        .body(userService.saveUser(request.body(UserInfo.class))))
                // If a filter logic parameter is executed and save release is carried, bad request will be returned with a message
                .filter((request, next) -> request.param("save").isPresent() ?
                        next.handle(request) :
                        ServerResponse.status(HttpStatus.BAD_REQUEST).body("no save"))
                .build();
    }

Through the filter method, we can achieve logging, security policy, cross domain and other functions.

2.3 how to use interceptors

The interceptor API of Spring MVC is not provided when using functional programming style, but similar filter pre / post processing mechanism is provided to achieve the same effect.

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

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

2.4 how to conduct unified treatment

In the traditional way, each Controller deals with business in a specific single domain. UserController deals with User related businesses. We will add a unified prefix identifier / V1 / User to it. Ordercontroller deals with Order related businesses and adds a unified prefix identifier / v1/order to it. Aggregation of the same business interface is more conducive to maintenance. Functional programming can be implemented 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();
}

You can also use RouterFunctions.route().nest related methods for implementation. After grouping and aggregating these routes, the filter, interceptor and exception handling can be unified. 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()))
            // Unified exception handling for the above routes
            .onError(RuntimeException.class,
                    (throwable, serverRequest) -> ServerResponse
                            .status(HttpStatus.BAD_REQUEST)
                            .body("bad req"))
            .build();
}

3. Summary

In this paper, the Spring MVC functional development and the traditional development of the equivalent features (filter, interceptor, grouping aggregation, etc.) are simply explained, more practical application. Functional style development is more flexible, but it also makes developers who are accustomed to command-based programming a little bit uncomfortable, but it is now more and more widely used. So - / if there are students who are willing to be engaged in programming development for a long time, they still need to master. The demo of this article can be obtained by paying attention to the official account number: Felordcn reply mvcfun.

Pay attention to the official account: Felordcn for more information

Personal blog: https://felord.cn

Tags: Programming Spring less Java

Posted on Sun, 17 May 2020 21:59:11 -0400 by jacomus