Spring MVC 函數式編程進階

1. 前言

上一篇對 Spring MVC 的函數式接口編程進行了簡單入門,讓不少不知道的同窗見識了這種新操做。也有反應這種看起來沒有傳統寫法順眼,其實你們都同樣。可是咱們仍是要勇於嘗試新事物。Java Lambada 剛出來也是被人各類吐槽,如今我在不少項目都見到了它的身影。好了轉回正題,本文是對上一篇的延伸,咱們繼續對 Functional Endpoint 進行一些瞭解和運用。範式轉換其實上一篇已經介紹差很少了,可是一旦你初次接觸這種方式每每會面臨新的問題。html

2. 新的問題

在使用這種風格時咱們也會遇到一些新的問題。接下來咱們將經過舉例來一步步解決這些問題。java

2.1 如何異常處理

接口異常處理是必須的。改爲函數式風格後異常能夠這樣處理:spring

/** * 接口附帶異常處理邏輯. * * @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"))))
                // 異常處理
                .onError(RuntimeException.class,
                        (e, request) -> EntityResponse.fromObject(e.getMessage())
                                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                                .build())
                .build();
    }
複製代碼

你可使用上面的 onError 方法及其重載方法進行接口的異常處理。可是傳統方法有統一異常處理啊!不要捉急,後面咱們也會進行統一異常的處理。編程

2.2 如何使用過濾器

我還有很多 Spring MVC 在使用過濾器呢,使用這種風格如何編寫過濾器,上一篇漏掉了一個處理過濾器的函數式接口HandlerFilterFunction 。咱們經過該接口來對請求進行過濾:跨域

/** * 對特定接口指定過濾器. * * @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))))
                // 執行了一個過濾器邏輯 參數攜帶了 save 放行 不然返回 bad request 並附帶消息
                .filter((request, next) -> request.param("save").isPresent() ?
                        next.handle(request) :
                        ServerResponse.status(HttpStatus.BAD_REQUEST).body("no save"))
                .build();
    }
複製代碼

經過 filter 方法咱們能夠實現日誌、安全策略、跨域等功能。spring-mvc

2.3 如何使用攔截器

使用函數式編程風格時並無提供 Spring MVC 的攔截器 API,可是提供了相似過濾器前置/後置處理機制以達到一樣的效果。安全

public RouterFunction<ServerResponse> getUserByName() {
        return RouterFunctions.route()
                .GET("/user/{username}",
                        request -> ServerResponse.ok()
                         .body(userService.getByName(request.pathVariable("username"))))
                // 前置處理 打印 path
                .before(serverRequest -> {
                    log.info(serverRequest.path());
                    return serverRequest;
                })
                // 後置處理 若是響應狀態爲200 則打印 response ok
                .after(((serverRequest, serverResponse) -> {
                    if (serverResponse.statusCode() == HttpStatus.OK) {
                        log.info("response ok");
                    }
                    return serverResponse;
                })).build();
    }
複製代碼

當你請求/user/{username}時, beforeafter 方法將會分別進行前置和後置處理。mvc

2.4 如何進行統一處理

傳統方式咱們每一個Controller 處理的都是特定單一領域的業務,UserController 處理 User相關業務,咱們會給它添加一個統一的前綴標識 /v1/user;OrderController處理 Order 相關業務,給它添加一個統一的前綴標識 /v1/order。對相同得業務接口進行聚合更加有利於維護使用函數式編程咱們能夠經過如下方式實現:函數式編程

@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();
}
複製代碼

你也可使用 RouterFunctions.route().nest相關的方法進行實現。並且對這些路由進行分組聚合以後就能夠統一過濾器、攔截器、異常處理。例如上一篇提到的統一異常問題:函數

@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()))
            // 對上述路由進行統一的異常處理
            .onError(RuntimeException.class,
                    (throwable, serverRequest) -> ServerResponse
                            .status(HttpStatus.BAD_REQUEST)
                            .body("bad req"))
            .build();
}
複製代碼

3. 總結

本文主要對 Spring MVC 函數式開發和傳統開發中等效的特性(過濾器、攔截器、分組聚合等)進行了簡單的說明,更加貼合於實際運用。函數式風格開發更加靈活,可是一樣讓習慣命令式編程的開發者有點不適應,可是目前愈來愈被廣泛的應用。因此-/若是有志於長期從事編程開發的同窗來講,仍是須要掌握的。本文的 demo 可經過關注公衆號:Felordcn回覆 mvcfun 獲取。

關注公衆號:Felordcn獲取更多資訊

我的博客:https://felord.cn

相關文章
相關標籤/搜索