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包配置下就可 ...
随机推荐
- MySQL——MySQL体系结构
1.连接层 2.SQL层: (1)将接收到的SQL语句,解析成执行计划 (2)查询优化器 ---->选择最优的执行计划,交给存储引擎 (3)查询缓存: 缓存执行计划 (4)附加功能:权限. 语法 ...
- Django图片上传和前端展示
1 - 模型 class Book(models.Model): #定义图书模型 book_name = models.CharField(max_length=100,verbose_name='书 ...
- C# 爬虫框架实现 流程_各个类开发
目录链接:C# 爬虫框架实现 概述 对比通用爬虫结构,我将自己写的爬虫分为五个类实现: Spider主类:负责设置爬虫的各项属性 Scheduler类:负责提供URL到下载类,接收URL并做去重 Do ...
- 【SpringCloud技术专题】「Eureka源码分析」从源码层面让你认识Eureka工作流程和运作机制(上)
前言介绍 了解到了SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud中用的最多的注册中心就是Eureka了,所以深入Eureka的原理和源码,接下来我们要进 ...
- bean.xml配置数据源和读取配置文件配置数据源
一.bean.xml配置数据源 bean.xml装配bean,依赖注入其属性的时候,对应实体类中属性一定要有set方法, 二.读取配置文件配置数据源 1.配置文件 bean.xml配置: classp ...
- scrum项目冲刺_day06总结
摘要:今日完成任务. 1.服务器部署完成 2.由于将数据库放到了服务器上,搜索功能需要修改 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成 ...
- Shell系列(32)- 双分支if语句判断Apache服务是否启动
#!/bin/bash #截取httped进程,并把结果赋予变量test test=$(ps -aux | grep "httpd" | grep -v "grep&qu ...
- 接口Mock测试
什么是Mock测试? Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的比较复杂的对象(如 JDB ...
- python全局变量的定义
第一:如定义在类或者函数体外,在函数或者类中引用需要用到 global声明 temp_t = "ceshi" def tmp1(): global temp_t temp_t =1 ...
- Java安全之 ClassLoader类加载器
Java安全之 ClassLoader类加载器 0x00 前言 前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那 ...