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

最新文章

  1. TEA,XXTEA介绍,对称加密
  2. Arcgis Engine 切割图斑(ITool)
  3. c++之路进阶——bzoj3343(教主的魔法)
  4. mysql+tomcat+spring 配置心得(从0开始搭环境)
  5. 2014-9-17二班----7 web project
  6. mysql distinct跟group by性能
  7. Find the capitals
  8. ASP.NET中的SQL注入攻击与防护
  9. BZOJ 1756: Vijos1083 小白逛公园
  10. 高性能 TCP &amp;amp; UDP 通信框架 HP-Socket v3.2.3 正式公布
  11. 【原创】修复ios输入框获取焦点时不支持fixed的bug
  12. ajax json struts JSP传递消息到action返回数据到JSP
  13. .tar.gz 和.tgz 解压
  14. Category Theory: 01 One Structured Family of Structures
  15. python调用.net动态库
  16. jq判断滚动条向上还是向下
  17. DXP 内电层分割
  18. java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列
  19. VS2015编译VLC2.2.1(under WIN7-64)&lt;转&gt;
  20. Visual Studio 当前不会命中断点的问题 编辑pdf文件

热门文章

  1. JDK方法区、元空间区别 &amp; String.intern相关面试题
  2. Docker(23)- 注册 docker hub 的账号
  3. FastReport合并多份报表为一份预览打印
  4. 轻量化模型系列--GhostNet:廉价操作生成更多特征
  5. sticky -- position定位属性sticky值之粘性定位;
  6. Markdown快捷键详解(源自狂神说Java视频,整理版)
  7. 【转】shell中的$0 $n $# $* $@ $? $$ 变量 if case for while
  8. js模块化开发 AMD CMD Commonjs
  9. 『Python』列表生成式、生成器与迭代器
  10. Sql 小技巧(持续汇总)