Spring 5 发行已经好几年了,里面提出了好几个新点子。其中一个就是 RouterFunction,这是个什么东西呢?

Spring框架给我们提供了两种http端点暴露方式来隐藏servlet原理,一种就是这多年大家都在使用的基于注解的形式@Controller@RestController 以及其他的注解如@RequestMapping@GetMapping等等。另外一种是基于路由配置RouterFunctionHandlerFunction的,称为“函数式WEB”。这篇文章我们就是来介绍后面这种函数式web的。

为什要说这个东西呢?老老实实用注解不好吗?一个原因是它既然存在,我们就该学习 。第二个原因是WebFlux推荐使用这个方式,而Spring在将来有可能推荐使用WebFlux而非MVC(Spring mvc可能会被废弃)。所以我们需要提早掌握。

wait...你不是来宣传WebFlux的吧?放心,这篇文章里再也不会出现WebFlux了

既然基于注解的MVC和函数式开发是等效的,那我们就先看下他们的对比。下面分别是用两种风格实现的代码:

@RestController
@RequestMapping("/model/building")
@Slf4j
public class ModelBuildingController { @Autowired
private IModelBuildingService modelBuildingService; @GetMapping("/{entId}/stations")
public PagedResult<StationVO> getStations(@PathVariable("entId") Long entId) {
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
List<StationVO> stationVoList = TransformUtils.transformList(stationBoList, StationVO.class);
return PagedResult.success(stationVoList);
}
}

再看函数式风格

import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse; @Configuration
public class ModelBuildingRouting { @Autowired
private IModelBuildingService modelBuildingService; @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters() {
return RouterFunctions.route(GET("/model/building/{entId}/stations"),
request -> {
Long entId = Long.valueOf(request.pathVariable("entId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
return ServerResponse.ok().body(PagedResult.success(stationVoList));
}
);
}
}

我们可以稍作修改,来看下效果:

@Configuration
public class ModelBuildingRouting { @Autowired
private IModelBuildingService modelBuildingService; @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters() {
return RouterFunctions.route(GET("/model/building/{entId}/stations"),
request -> {
Long entId = Long.valueOf(request.pathVariable("entId"));
// List<StationBO> stationBoList = modelBuildingService.getStations(entId);
List<StationVO> stationVoList = new ArrayList<>(1);
StationVO e = new StationVO();
e.setCode("123");
e.setName("xyz");
e.setAliasCode("AA");
e.setType("TT");
stationVoList.add(e);
return ServerResponse.ok().body(PagedResult.success(stationVoList));
}
);
}
}

返回值当然是一样的了

如果你复制这段代码后编译报错,可能是引入了webflux依赖,我们这里使用的是web依赖,注意看一下import的类

路由嵌套

在惊喜之余,可能你在上面的代码中发现有一点小问题:使用Controller的时候,类上面是可以定义公共url前缀的,比如/model/building。但是使用函数式,貌似每个Url都要自己拼上这一段。

其实,这两种东西都是spring自己搞的,它不可能削弱新东西的表达能力。那应该怎么用呢?

RouterFunctions提供了一个方法nest,可以把路由组织起来。修改上面的代码为

import static org.springframework.web.servlet.function.RequestPredicates.GET;
import static org.springframework.web.servlet.function.RequestPredicates.path; @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters() {
return RouterFunctions.nest(path("/model/building"),
RouterFunctions.route(GET("/{entId}/stations"),
request -> {
Long entId = Long.valueOf(request.pathVariable("entId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
return ServerResponse.ok().body(PagedResult.success(stationVoList));
}
));
}

增加路由

在controller中可以任意增加新的Action方法,只要使用RequestMapping标注就行,这样发布就能立即生效。那在RouterFunction中怎么增加更多路由呢?

RouterFunctions提供了一个方法andRoute,可以添加更多的路由。修改上面的代码为

@Bean
public RouterFunction<ServerResponse> getModelBuildingRouters() {
return RouterFunctions.nest(path("/model/building"),
RouterFunctions.route(GET("/{entId}/stations"),
request -> {
Long entId = Long.valueOf(request.pathVariable("entId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
return ServerResponse.ok().body(PagedResult.success(stationVoList));
}
).andRoute(GET("/{stationId}/device-types"),
request -> {
String stationId = request.pathVariable("stationId");
List<DeviceTypeBO> deviceTypeBoList = modelBuildingService.getDeviceTypes(stationId);
List<DeviceTypeVO> deviceTypeVoList = TransformUtils.transformList(deviceTypeBoList, DeviceTypeVO.class);
return ServerResponse.ok().body(deviceTypeVoList);
}
));
}

现在就有两个url了:/model/building/{entId}/stations/model/building/{stationId}/device-types

你可能会说:这不是没有必要吗,我也可以再增加一个Bean,变成下面这样:

@Configuration
public class ModelBuildingRouting { @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters(IModelBuildingService modelBuildingService) {
return RouterFunctions.nest(path("/model/building"),
RouterFunctions.route(GET("/{entId}/stations"),
request -> {
Long entId = Long.valueOf(request.pathVariable("entId"));
System.out.println(entId);
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
return ServerResponse.ok().body(PagedResult.success(stationBoList));
}
));
} @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters1(IModelBuildingService modelBuildingService) {
return RouterFunctions.nest(path("/model/building"),
RouterFunctions.route(GET("/{stationId}/device-types"),
request -> {
String stationId = request.pathVariable("stationId");
List<DeviceTypeBO> deviceTypeBoList = modelBuildingService.getDeviceTypes(stationId);
List<DeviceTypeVO> deviceTypeVoList = TransformUtils.transformList(deviceTypeBoList, DeviceTypeVO.class);
return ServerResponse.ok().body(deviceTypeVoList);
}
));
}
}

的确,这样也是可以的。甚至可以建多个@Configuration类,每个类分一些路由都行。但是,我们是通过类、方法、组织来管理路由系统的。我们当然期望尽量通过一个类、几个方法来管理全部的路由。

HandlerFunction

如果你留意一下route()方法,可以看到这个方法的第二个参数类型是org.springframework.web.servlet.function.HandlerFunction。从前面的逻辑也可以看出来,这个函数式接口中方法的入参是请求request,返回是业务数据。所以很明显,这个就是网络请求的处理器。

为了风格简洁,通常我们不会把业务逻辑写在Routing这个Configuration中。因为前面说了,我们的所有路由维护都在一起,如果连逻辑也写在这,那这个类的大小就不可控了。另外还有一个问题是,业务逻辑写在路由定义处,就会导致大量注入Service。不论是通过属性注入到类还是通过方法参数传入进来,数量上来都会比较丑陋。

所以和Controller的拆分一样,我们通过拆分Handler来组织业务逻辑。

新建Handler类:

@Component
public class ModelBuildingHandler {
@Autowired
private IModelBuildingService modelBuildingService; public ServerResponse getStations(ServerRequest req) {
Long entId = Long.valueOf(req.pathVariable("endId"));
List<StationBO> stationBoList = modelBuildingService.getStations(entId);
List<StationVO> stationVoList = TransformUtils.transformList(stationBoList, StationVO.class);
return ok().body(PagedResult.success(stationVoList));
} public ServerResponse getDeviceTypes(ServerRequest req) {
String stationId = req.pathVariable("stationId");
List<DeviceTypeBO> deviceTypeBoList = modelBuildingService.getDeviceTypes(stationId);
List<DeviceTypeVO> deviceTypeVoList = TransformUtils.transformList(deviceTypeBoList, DeviceTypeVO.class);
return ok().body(PagedResult.success(deviceTypeVoList));
}
}

可以看到,里面的方法和原来(long long ago)最初的controller中的逻辑几乎一样,只是参数和返回值固定成了ServerRequestServerResponse类型。

然后改造路由定义类,来使用这些handler:

@Configuration
public class RoutingConfig { @Bean
public RouterFunction<ServerResponse> getModelBuildingRouters(ModelBuildingHandler modelBuildingHandler) {
return RouterFunctions.nest(path("/model/building"),
RouterFunctions.route(GET("/{entId}/stations"), modelBuildingHandler::getStations)
.andRoute(GET("/{stationId}/device-types"), modelBuildingHandler::getDeviceTypes)
);
}
}

可以看到,这个类变得简洁多了,这样每个方法可以对应一个Handler,将其通过参数传入即可。

当然如果嫌模板代码太多可以创建父类,比如

public abstract class BaseHandler {
protected ServerResponse body(Object body) {
return ServerResponse.ok().body(body);
} protected String path(ServerRequest req, String path) {
return req.pathVariable(path);
}
}

Swagger

如果你开开心心改造完,发下原来大家都喜欢的swagger 文档再也不见了,是不是哭完还要把代码改回来?

其实不用哭了,不是有代码库版本管理的嘛

SpringFox的swagger版本是2。要想让swagger扫描到RouterFunction配置,需要升级到版本3。具体方法请看下一篇。

参考文档

Spring Functional Web Framework Guide

Functional Controllers in Spring MVC

Spring Boot RouterFunction

Migrating from SpringFox

Spring-webflux/WebMvc.fn with Functional Endpoints

Spring 5 MVC 中的 Router Function 使用的更多相关文章

  1. Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列

    Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列

  2. spring web mvc中遇到的错误以及学习小记(持续记录)

    错误:cvc-complex-type.2.4.a: 发现了以元素 'init-param' 开头的无效内容.应以 '{"http://java.sun.com/xml/ns/javaee& ...

  3. Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->Spring Framework中的spring web MVC模块

    spring framework中的spring web MVC模块 1.概述 spring web mvc是spring框架中的一个模块 spring web mvc实现了web的MVC架构模式,可 ...

  4. Spring Web MVC框架简介

    Web MVC framework框架 Spring Web MVC框架简介 Spring MVC的核心是`DispatcherServlet`,该类作用非常多,分发请求处理,配置处理器映射,处理视图 ...

  5. 4.Spring Web MVC处理请求的流程

  6. Spring - Sring MVC入门

    2.1.Spring Web MVC是什么 Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职 ...

  7. Spring官方文档翻译——15.1 介绍Spring Web MVC框架

    Part V. The Web 文档的这一部分介绍了Spring框架对展现层的支持(尤其是基于web的展现层) Spring拥有自己的web框架--Spring Web MVC.在前两章中会有介绍. ...

  8. spring web mvc第一天

    spring  web mvc 感觉就是高大上啊!啥都是配置文件就能够了.所以第一步就是弄清楚配置文件使用和总体框架的流程! Spring web mvc最重要的当然是Controller,也就是首先 ...

  9. Spring mvc 中使用 kaptcha 验证码

    生成验证码的方式有很多,个人认为较为灵活方便的是Kaptcha ,他是基于SimpleCaptcha的开源项目.使用Kaptcha 生成验证码十分简单并且参数可以进行自定义.只需添加jar包配置下就可 ...

随机推荐

  1. Http协议 压缩

    先说结论:Request和Response可以设置gzip压缩从而节省流量/带宽,这是一个不常见的问题.具体可见连接:HTTP 协议之压缩 最近测试代理服务器时,意外的发现使用urlopen请求百度首 ...

  2. adb 常用命令大全(6)- 模拟按键输入

    语法格式 input [<source>] <command> [<arg>...] 物理键 # 电源键 adb shell input keyevent 26 # ...

  3. Gitlab - 安装的社区版 Gitlab-ce,解决访问网页报502-Whoops, GitLab is taking too much time to respond的问题

    问题背景 在自己虚拟机(centos7)上装了 Gitlab-ce,就是社区版的 Gitlab,版本是 13.0+ 问题描述 浏览器访问 Gitlab 网站,报 502 问题翻译 502-Whoops ...

  4. Spring MVC拦截器浅析

    Spring MVC拦截器 重点:Spring MVC的拦截器只会拦截控制器的请求,如果是jsp.js.image.html则会放行. 什么是拦截器 运行在服务器的程序,先于Servlet或JSP之前 ...

  5. Intel® QAT 加速卡之IPSec示例

    Intel QAT 加速卡之IPSec示例 文章目录 Intel QAT 加速卡之IPSec示例 1. QAT处理IPSec入站报文 2. QAT处理IPSec出站报文 3. 组织架构 4. 示例源码 ...

  6. php-fpm进程数控制

    一.名词解释 CGI是Common Gateway Interface(通用网管协议),用于让交互程序和Web服务器通信的协议.负责处理URL的请求,启动一个进程,将客户端发送的数据作为输入,有Web ...

  7. python关键字--yield

    彻底理解Python中的yield

  8. FastAPI 学习之路(一)fastapi--高性能web开发框架

    fastapi是高性能的web框架.他的主要特点是:- 快速编码- 减少人为bug- 直观- 简易- 具有交互式文档 - 高性能 - 基于API的开放标准 支持python 3.6版本. 安装 pip ...

  9. TP6 前一个项目可以,第二个项目配置多域名绑定应用一直失效

    之前的域名类似: www.a.com     test.a.com    edu.a.com   ... 文件config/app.php中 'domain_bind' => [ 'test' ...

  10. CS:APP Chapter-6 存储器层次系统-读书笔记

    存储器层次系统 笔记,应该不是一个大而全的文件,笔记应该是提纲挈领,是对思想的汇总浓缩,如果追求详实的内容反而是丢了初心. 计算机是抽象的,它的设计者努力让计算机变得简单,在设计上高度抽象,而计算机的 ...