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

相关文章
相关标签/搜索