SpringMVC 函数式编程进阶

简介:

Spring MVC 函数式编程进阶

  1. 前言
    上一篇对 Spring MVC 的函数式接口编程进行了简单入门,让很多不知道的同学见识了这种新操作。也有反应这种看起来没有传统写法顺眼,其实大家都一样。但是我们还是要敢于尝试新事物。Java Lambada 刚出来也是被人各种吐槽,现在我在很多项目都见到了它的身影。好了转回正题,本文是对上一篇的延伸,我们继续对 Functional Endpoint 进行一些了解和运用。范式转换其实上一篇已经介绍差不多了,但是一旦你初次接触这种方式往往会面临新的问题。
  2. 新的问题
    在使用这种风格时我们也会遇到一些新的问题。接下来我们将通过举例来一步步解决这些问题。

2.1 如何异常处理
接口异常处理是必须的。改成函数式风格后异常可以这样处理:

/**
 * 接口附带异常处理逻辑.
 *
 * @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 方法我们可以实现日志、安全策略、跨域等功能。

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}时, before 和 after 方法将会分别进行前置和后置处理。

2.4 如何进行统一处理
传统方式我们每个Controller 处理的都是特定单一领域的业务,UserController 处理 User相关业务,我们会给它添加一个统一的前缀标识 /v1/user;OrderController处理 Order 相关业务,给它添加一个统一的前缀标识 /v1/order。对相同得业务接口进行聚合更加有利于维护使用函数式编程我们可以通过以下方式实现:

@Bean
RouterFunction 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 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();

}

  1. 总结
    本文主要对 Spring MVC 函数式开发和传统开发中等效的特性(过滤器、拦截器、分组聚合等)进行了简单的说明,更加贴合于实际运用。函数式风格开发更加灵活,但是同样让习惯命令式编程的开发者有点不适应,但是目前越来越被普遍的应用。所以-/如果有志于长期从事编程开发的同学来说,还是需要掌握的。本文的 demo 可通过关注公众号:Felordcn回复 mvcfun 获取。

原文地址https://www.cnblogs.com/felordcn/p/12908820.html

相关文章
|
4天前
|
前端开发 Java 程序员
从零基础手写Spring MVC框架,准备好进阶程序员了吗?
我们程序员大部分人都是野路子,不懂什么叫代码规范。写了一个月的代码,最后还得其他老司机花3天时间重构,相信大部分老司机都很头疼看新手的代码。
26 1
|
6月前
|
Java 程序员 应用服务中间件
【推荐】深入浅出学习Spring框架【上】
【推荐】深入浅出学习Spring框架【上】
39 0
|
7月前
|
前端开发 Java Spring
SpringMvc进阶
SpringMvc进阶
26 1
|
4天前
|
算法 编译器 程序员
【C++入门到精通】 Lambda表达式 C++11 [ C++入门 ]
【C++入门到精通】 Lambda表达式 C++11 [ C++入门 ]
12 1
|
6月前
|
存储 监控 Java
【推荐】深入浅出学习Spring框架【中】
【推荐】深入浅出学习Spring框架【中】
26 0
|
8月前
|
设计模式 前端开发 Java
SpringMVC重温知识点
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
55 0
SpringMVC重温知识点
|
XML Java 数据安全/隐私保护
Servlet实战编程
Servlet实战编程: 基于Servlet实现的简单登陆练习(附源码)
Servlet实战编程
|
监控 安全 Java
【精通函数式编程】(十一) CompletableFuture、反应式编程源码解析与实战
Future异步编程和CompletableFuture 接口都可以实现异步编程,我们通过源码深入理解其原理和设计的思想,Java9中提供了反应式编程(Flow API)我们分析其源码并提供一个响应式查询实战。
|
设计模式 开发框架 前端开发
【重温SSM框架系列】5 - SpringMVC快速入门(开发流程代码实现)
【重温SSM框架系列】5 - SpringMVC快速入门(开发流程代码实现)
158 0
【重温SSM框架系列】5 - SpringMVC快速入门(开发流程代码实现)
|
Java 关系型数据库 MySQL
Spring框架基础知识(05)
Spring框架基础知识
150 0
Spring框架基础知识(05)