1. 前言

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

2. 新的问题

在使用这种风格时我们也会遇到一些新的问题。接下来我们将通过举例来一步步解决这些问题。

2.1 如何异常处理

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

  1. /**
  2. * 接口附带异常处理逻辑.
  3. *
  4. * @param userService the user service
  5. * @return the user by name with error handle
  6. */
  7. public RouterFunction<ServerResponse> withErrorHandle() {
  8. return RouterFunctions.route()
  9. .GET("/userwitherrorhandle/{username}",
  10. request -> ServerResponse.ok()
  11. .body(userService.getByName(request.pathVariable("username"))))
  12. // 异常处理
  13. .onError(RuntimeException.class,
  14. (e, request) -> EntityResponse.fromObject(e.getMessage())
  15. .status(HttpStatus.INTERNAL_SERVER_ERROR)
  16. .build())
  17. .build();
  18. }

你可以使用上面的 onError 方法及其重载方法进行接口的异常处理。但是传统方法有统一异常处理啊!不要捉急,后面我们也会进行统一异常的处理。

2.2 如何使用过滤器

我还有不少 Spring MVC 在使用过滤器呢,使用这种风格如何编写过滤器,上一篇漏掉了一个处理过滤器的函数式接口HandlerFilterFunction 。我们通过该接口来对请求进行过滤:

  1. /**
  2. * 对特定接口指定过滤器.
  3. *
  4. * @param userService the user service
  5. * @return the router function
  6. */
  7. public RouterFunction<ServerResponse> withFilter() {
  8. return RouterFunctions.route().POST("/save",
  9. request -> ServerResponse.ok()
  10. .body(userService.saveUser(request.body(UserInfo.class))))
  11. // 执行了一个过滤器逻辑 参数携带了 save 放行 否则返回 bad request 并附带消息
  12. .filter((request, next) -> request.param("save").isPresent() ?
  13. next.handle(request) :
  14. ServerResponse.status(HttpStatus.BAD_REQUEST).body("no save"))
  15. .build();
  16. }

通过 filter 方法我们可以实现日志、安全策略、跨域等功能。

2.3 如何使用拦截器

使用函数式编程风格时并没有提供 Spring MVC 的拦截器 API,但是提供了类似过滤器前置/后置处理机制以达到同样的效果。

  1. public RouterFunction<ServerResponse> getUserByName() {
  2. return RouterFunctions.route()
  3. .GET("/user/{username}",
  4. request -> ServerResponse.ok()
  5. .body(userService.getByName(request.pathVariable("username"))))
  6. // 前置处理 打印 path
  7. .before(serverRequest -> {
  8. log.info(serverRequest.path());
  9. return serverRequest;
  10. })
  11. // 后置处理 如果响应状态为200 则打印 response ok
  12. .after(((serverRequest, serverResponse) -> {
  13. if (serverResponse.statusCode() == HttpStatus.OK) {
  14. log.info("response ok");
  15. }
  16. return serverResponse;
  17. })).build();
  18. }

当你请求/user/{username}时, beforeafter 方法将会分别进行前置和后置处理。

2.4 如何进行统一处理

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

  1. @Bean
  2. RouterFunction<ServerResponse> userEndpoints(UserController userController) {
  3. return RouterFunctions.route()
  4. .path("/v2/user", builder -> builder
  5. // /get/{username} -> /v2/user//get/{username}
  6. .add(userController.getUserByName()
  7. // /del/{username} -> /v2/user//del/{username}
  8. .and(userController.delUser()
  9. // /save -> /v2/user/save
  10. .and(userController.saveUser()
  11. // /update -> /v2/user/update
  12. .and(userController.updateUser())))))
  13. .build();
  14. }

你也可以使用 RouterFunctions.route().nest相关的方法进行实现。而且对这些路由进行分组聚合之后就可以统一过滤器、拦截器、异常处理。例如上一篇提到的统一异常问题:

  1. @Bean
  2. RouterFunction<ServerResponse> nestEndpoints(UserController userController) {
  3. return RouterFunctions.route().nest(RequestPredicates.path("/v1/user"),
  4. builder -> builder
  5. .add(userController.getUserByName())
  6. .add(userController.delUser())
  7. .add(userController.saveUser())
  8. .add(userController.updateUser()))
  9. // 对上述路由进行统一的异常处理
  10. .onError(RuntimeException.class,
  11. (throwable, serverRequest) -> ServerResponse
  12. .status(HttpStatus.BAD_REQUEST)
  13. .body("bad req"))
  14. .build();
  15. }

3. 总结

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

关注公众号:Felordcn 获取更多资讯

个人博客:https://felord.cn

Spring MVC 函数式编程进阶的更多相关文章

  1. Scala函数式编程进阶

    package com.dtspark.scala.basics /** * 函数式编程进阶: * 1,函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量: * 2, 函数更长用的方式 ...

  2. Spark函数式编程进阶

    函数式编程进阶 1.函数和变量一样作为Scala语言的一等公民,函数可以直接复制给变量: 2.函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是匿名函数赋值给 ...

  3. Python函数式编程(进阶2)

    转载请标明出处: http://www.cnblogs.com/why168888/p/6411915.html 本文出自:[Edwin博客园] Python函数式编程(进阶2) 1. python把 ...

  4. Scala实战高手****第12课:Scala函数式编程进阶(匿名函数、高阶函数、函数类型推断、Currying)与Spark源码鉴赏

    /** * 函数式编程进阶: * 1.函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量 * 2.函数更常用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称 ...

  5. 使用Spring MVC创建 REST API

    1.REST的基础知识 当谈论REST时,有一种常见的错误就是将其视为“基于URL的Web服务”——将REST作为另一种类型的远程过程调用(remote procedurecall,RPC)机制,就像 ...

  6. [读后感]spring Mvc 教程框架实例以及系统演示下载

    [读后感]spring Mvc 教程框架实例以及系统演示下载 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致&qu ...

  7. 编程体系结构(08):Spring.Mvc.Boot框架

    本文源码:GitHub·点这里 || GitEE·点这里 一.Spring框架 1.框架概述 Spring是一个开源框架,框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 ...

  8. Python进阶:函数式编程实例(附代码)

    Python进阶:函数式编程实例(附代码) 上篇文章"几个小例子告诉你, 一行Python代码能干哪些事 -- 知乎专栏"中用到了一些列表解析.生成器.map.filter.lam ...

  9. J2EE进阶(十三)Spring MVC常用的那些注解

    Spring MVC常用的那些注解 前言 Spring从2.5版本开始在编程中引入注解,用户可以使用@RequestMapping, @RequestParam,@ModelAttribute等等这样 ...

随机推荐

  1. seo 回忆录百度基本概念(一)

    前言 我以前的博客自己做的seo,现在拿来和大家一起交流,是白帽哈,黑帽的不敢发,也不敢学[微笑]. 正文 为什么做seo 做seo说到底就是为了排名.为什么需要排名呢?因为现在人比较懒,只会去查看第 ...

  2. Jquery中 $.Ajax() 参数详解

    1.url:要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type:要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如pu ...

  3. 使用JAVA API编程实现简易Habse操作

    使用JAVA API编程实现下面内容: 1.创建<王者荣耀>游戏玩家信息表gamer,包含列族personalInfo(个人信息).recordInfo(战绩信息).assetsInfo( ...

  4. php://input和parse_str()使用

    php://input可以读取没有处理过的POST数据,总结起来就是, 在用$_POST获取不到由APP或者一些接口的回调数据时,就用php://input试试 实例 index.php <fo ...

  5. python学习18类4之静态类

    '''''''''类的静态方法.普通方法.类方法 静态方法: 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用. 普通方法: ...

  6. Unity 游戏框架搭建 2019 (三十九、四十一) 第四章 简介&方法的结构重复问题&泛型:结构复用利器

    第四章 简介 方法的结构重复问题 我们在上一篇正式整理完毕,从这一篇开始,我们要再次进入学习收集示例阶段了. 那么我们学什么呢?当然是学习设计工具,也就是在上篇中提到的关键知识点.这些关键知识点,大部 ...

  7. 基于NFS共享存储实现KVM虚拟机动态迁移

    基于NFS共享存储实现KVM虚拟机动态迁移 一:配置环境 二:安装相关的依赖包 三:实现NFS共享存储 四:KVM机配置相同的步骤 五:安装KVM01安装虚拟机 六:实现迁移  实验初始配置:所有主机 ...

  8. awk命令及随机数的产生

    3.sed 操作,将文件第9行至第15行的数据复制到第十六行 sed -i  '9,15H;16G' 文件 4.用awk获取文件中的三行的倒数第二列字段 awk -F":" 'NR ...

  9. HDU 5451 Best Solver

    没有意识到循环节最大是M^2,用域Z_M下二阶可逆矩阵群的最大循环节(M^2-1)*(M^2-M)来做,考虑大数乘法,矩阵乘法,做到吐血.将代码贴在下方留作纪念. #include<bits/s ...

  10. Windows 怎么启动 apache

    在可执行目录下找到httpd.exe命令,然后运行cmd,执行类似以下命令:C:\"Program Files"\"Apache Software Foundation& ...