Spring 5 MVC 中的 Router Function 使用
Spring 5 发行已经好几年了,里面提出了好几个新点子。其中一个就是 RouterFunction,这是个什么东西呢?
Spring框架给我们提供了两种http端点暴露方式来隐藏servlet原理,一种就是这多年大家都在使用的基于注解的形式@Controller
或@RestController
以及其他的注解如@RequestMapping
、@GetMapping
等等。另外一种是基于路由配置RouterFunction
和HandlerFunction
的,称为“函数式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中的逻辑几乎一样,只是参数和返回值固定成了ServerRequest
和ServerResponse
类型。
然后改造路由定义类,来使用这些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 使用的更多相关文章
- Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列
Spring Web MVC中的页面缓存支持 ——跟我学SpringMVC系列
- spring web mvc中遇到的错误以及学习小记(持续记录)
错误:cvc-complex-type.2.4.a: 发现了以元素 'init-param' 开头的无效内容.应以 '{"http://java.sun.com/xml/ns/javaee& ...
- 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架构模式,可 ...
- Spring Web MVC框架简介
Web MVC framework框架 Spring Web MVC框架简介 Spring MVC的核心是`DispatcherServlet`,该类作用非常多,分发请求处理,配置处理器映射,处理视图 ...
- 4.Spring Web MVC处理请求的流程
- Spring - Sring MVC入门
2.1.Spring Web MVC是什么 Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职 ...
- Spring官方文档翻译——15.1 介绍Spring Web MVC框架
Part V. The Web 文档的这一部分介绍了Spring框架对展现层的支持(尤其是基于web的展现层) Spring拥有自己的web框架--Spring Web MVC.在前两章中会有介绍. ...
- spring web mvc第一天
spring web mvc 感觉就是高大上啊!啥都是配置文件就能够了.所以第一步就是弄清楚配置文件使用和总体框架的流程! Spring web mvc最重要的当然是Controller,也就是首先 ...
- Spring mvc 中使用 kaptcha 验证码
生成验证码的方式有很多,个人认为较为灵活方便的是Kaptcha ,他是基于SimpleCaptcha的开源项目.使用Kaptcha 生成验证码十分简单并且参数可以进行自定义.只需添加jar包配置下就可 ...
随机推荐
- python实现分水岭算法分割遥感图像
1. 定义 分水岭算法(watershed algorithm)可以将图像中的边缘转化为"山脉",将均匀区域转化为"山谷",在这方面有助于分割目标. 分水岭算法 ...
- 未能找到源类型“DbSet<T>”的查询模式的实现。未找到“Select”
使用EF6.0的模型优先模式进行开发,遇到了报错,如下图 后来发现是没引用using System.Linq; 引用后就不报错了
- 逐条更新数据 sql
declare @tid int declare @fid int declare @i int declare @j int set @j=(select count(*) from ...
- POJ1804——Brainman(水题)
解题思路: 一个乱序序列的 逆序数 = 在只允许相邻两个元素交换的条件下,得到有序序列的交换次数 直接求逆序数 把S[i]和s[i+1~n]的元素逐个比较,如果s[i] > s[k] (k∈[i ...
- 浅析Java中的static关键字
关键点 <Java编程思想>对static方法的描述:"static方法就是没有this的方法.在static方法内部不能调用非静态方法,反过来是可以的.而且可以在没有创建对象的 ...
- 《Android自动化环境搭建》
一.安装JDK并配置环境变量 1:在Java官网上下载本机系统相对应的jdk文件安装,直接下一步一步到位 2:配置JAVA_HOME 新建 JAVA_HOME 环境变量,变量值是所安装JDK 的路径, ...
- pandas学习小记
pandas操作整理 导入数据: pd.read_csv(filename):从CSV文件导入数据 pd.read_table(filename):从限定分隔符的文本文件导入数据 pd.read_ex ...
- Python-对Pcap文件进行处理,获取指定TCP流
通过对TCP/IP协议的学习,本人写了一个可以实现对PCAP文件中的IPV4下的TCP流提取,以及提取指定的TCP流,鉴于为了学习,没有采用第三方包解析pcap,而是对bytes流进行解析,其核心思想 ...
- centos 关于yum无法使用
一.网络问题 1.1 ping # 确认网络是否可以ping通, 通则不是网络问题(跳过), 不通则是网络问题(往下操作) ping www.baidu.com 1.2 检查网络模式 1.关闭虚拟机 ...
- P4389-付公主的背包【生成函数,多项式exp】
正题 题目链接:https://www.luogu.com.cn/problem/P4389 题目大意 \(n\)种物品,第\(i\)种大小为\(v_i\),数量无限.对于每个\(s\in[1,m]\ ...