首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 5 MVC 中的 Router Function 使用

Spring 5 MVC 中的 Router Function 使用

作者头像
冬夜先生
修改2021-10-28 10:16:30
1.2K0
修改2021-10-28 10:16:30
举报
文章被收录于专栏:csicocsico

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 文档再也不见了,是不是哭完还要把代码改回来?

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

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 路由嵌套
  • 增加路由
  • HandlerFunction
  • Swagger
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档