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. python实现分水岭算法分割遥感图像

    1. 定义 分水岭算法(watershed algorithm)可以将图像中的边缘转化为"山脉",将均匀区域转化为"山谷",在这方面有助于分割目标. 分水岭算法 ...

  2. 未能找到源类型“DbSet<T>”的查询模式的实现。未找到“Select”

    使用EF6.0的模型优先模式进行开发,遇到了报错,如下图 后来发现是没引用using System.Linq; 引用后就不报错了

  3. 逐条更新数据 sql

    declare @tid int        declare @fid int declare @i int declare @j int set @j=(select count(*) from ...

  4. POJ1804——Brainman(水题)

    解题思路: 一个乱序序列的 逆序数 = 在只允许相邻两个元素交换的条件下,得到有序序列的交换次数 直接求逆序数 把S[i]和s[i+1~n]的元素逐个比较,如果s[i] > s[k] (k∈[i ...

  5. 浅析Java中的static关键字

    关键点 <Java编程思想>对static方法的描述:"static方法就是没有this的方法.在static方法内部不能调用非静态方法,反过来是可以的.而且可以在没有创建对象的 ...

  6. 《Android自动化环境搭建》

    一.安装JDK并配置环境变量 1:在Java官网上下载本机系统相对应的jdk文件安装,直接下一步一步到位 2:配置JAVA_HOME 新建 JAVA_HOME 环境变量,变量值是所安装JDK 的路径, ...

  7. pandas学习小记

    pandas操作整理 导入数据: pd.read_csv(filename):从CSV文件导入数据 pd.read_table(filename):从限定分隔符的文本文件导入数据 pd.read_ex ...

  8. Python-对Pcap文件进行处理,获取指定TCP流

    通过对TCP/IP协议的学习,本人写了一个可以实现对PCAP文件中的IPV4下的TCP流提取,以及提取指定的TCP流,鉴于为了学习,没有采用第三方包解析pcap,而是对bytes流进行解析,其核心思想 ...

  9. centos 关于yum无法使用

    一.网络问题 1.1 ping # 确认网络是否可以ping通, 通则不是网络问题(跳过), 不通则是网络问题(往下操作) ping www.baidu.com 1.2 检查网络模式 1.关闭虚拟机 ...

  10. P4389-付公主的背包【生成函数,多项式exp】

    正题 题目链接:https://www.luogu.com.cn/problem/P4389 题目大意 \(n\)种物品,第\(i\)种大小为\(v_i\),数量无限.对于每个\(s\in[1,m]\ ...