Spring 5 中一个非常重要的更新就是增加了响应式web开发WebFlux,并且推荐使用函数式风格(RouterFunction
和 HandlerFunction
)来开发WebFlux。对于之前主流的MVC开发模式,Spring也顺道给它提供了和WebFlux函数式开发几乎一致的方式(见上文《Spring 5 MVC 中的 Router Function 使用》)。这样,响应式WebFlux和非响应式MVC都可以通过Controller
来实现,也都可以通过RouterFunction
来实现。
但是Spring推荐使用函数式方式;而且听说在所有场景也推荐使用WebFlux,后续有可能废弃掉非响应式MVC
对于通过Controller
来实现的接口,swagger可以直接扫描处理。使用了RouterFunction
的怎么办呢?这篇文章我们来看一下如果通过springdoc这个项目来实现函数式接口的文档生成。
pom依赖
假设你使用的是maven。如果使用gradle我估计也差不多
要使用springdoc-openapi,需要添加它的依赖。一般需要两个,第一个是基础公共依赖:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.9</version>
</dependency>
另一个根据对象不同需要引入的也不同。我们这里先处理非响应式MVC的接口,需要依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webmvc-core</artifactId>
<version>1.5.9</version>
</dependency>
依赖的版本号可以自己去查一下最新的
RouterOperation
接下来需要去RouterFunction上面定义swagger的显示信息,在controller
中使用的注解是io.swagger.v3.oas.annotations.Operation
(如果是版本2使用的注解是io.swagger.annotations.ApiOperation
),现在需要使用org.springdoc.core.annotations.RouterOperation
。以《Spring 5 MVC 中的 Router Function 使用》中的接口为例,需要这样写
@Configuration
public class RoutingConfig {
@Bean
@RouterOperations({
@RouterOperation(
path = "/model/building/{entId}/stations",
beanClass = ModelBuildingHandler.class,
beanMethod = "getStations",
method = RequestMethod.GET,
operation = @Operation(
operationId = "getStations",
parameters = @Parameter(
name = "entId",
in = ParameterIn.PATH,
required = true,
description = "企业ID"
)
)
)
})
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)
);
}
}
不同于Controller
中的接口会被自动发现,这里虽然有两个方法但是我们只定义了一个RouterOperation
操作,会导致看不到第二个方法。
我们修改一下application.properties就能看到效果了:
springdoc.packagesToScan=要扫描的包
springdoc.pathsToMatch=/**
注意springdoc.packagesToScan这里,需要写一个包含了
RouterFunction
和HandlerFunction
实现的包。我当时只写了RouterFunction
所在的包,一直不成功
再响应增加一个RouterOperation
即可:
@RouterOperations({
@RouterOperation(
path = "/model/building/{entId}/stations",
beanClass = ModelBuildingHandler.class,
beanMethod = "getStations",
method = RequestMethod.GET,
operation = @Operation(
operationId = "getStations",
parameters = @Parameter(
name = "entId",
in = ParameterIn.PATH,
required = true,
description = "企业ID"
)
)
),
@RouterOperation( // 这里我省略了一些属性配置
path = "/model/building/{stationId}/device-types",
beanClass = ModelBuildingHandler.class,
beanMethod = "getDeviceTypes"
)
})
RequestBody
上面讲的都是GET的请求,如果需要设置请求体格式怎么办呢?
在RouterFunction中增加一个POST请求(下面最后一个url):
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)
.andRoute(POST("/devices/points/real-time-data"), modelBuildingHandler::getRealTimeData)
);
}
它对应的RouterOperation
如下:
@RouterOperation(
path = "/model/building/devices/points/real-time-data",
beanClass = ModelBuildingHandler.class,
beanMethod = "getRealTimeData",
operation = @Operation(
operationId = "getRealTimeData",
requestBody = @RequestBody(
required = true,
description = "请求体",
content = @Content(
schema = @Schema(implementation = RealTimeDataQueryVO.class)
)
)
)
)
要在Handler中拿到请求参数,使用body方法:RealTimeDataQueryVO queryVo = req.body(RealTimeDataQueryVO.class);
public ServerResponse getRealTimeData(ServerRequest req) {
RealTimeDataQueryVO queryVo;
try {
queryVo = req.body(RealTimeDataQueryVO.class);
log.info("请求参数{}", queryVo);
} catch (Exception e) {
throw new RuntimeException(e);
}
RealTimeDataResultBO resultBo = modelBuildingService.getRealTimeData(transferRealTimeDataQueryParam(queryVo));
return body(RdfaResult.success(transferRealTimeDataResult(resultBo)));
}
上面就是如何在函数式MVC编程中使用swagger。可以看到比较繁琐,3行代码对应了差不多30行swagger配置。
从swagger v2 迁移
如果之前的项目使用的是springfox之类的swagger版本2,需要将其依赖移除,并修改类上的注解。
主要的变更点如下图:
Swagger配置
这一步是可选的,几乎没什么必要。
可以像之前一样定义文档的归属、协议、版本等等:
@Configuration
public class DocConfig {
@Bean
public OpenAPI springShopOpenApi() {
return new OpenAPI()
.info(new Info().title("测试 API")
.description("样例程序")
.version("v0.0.1")
.license(new License()
.name("Apache 2.0我的协议")
.url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("外部文档链接")
.url("https://springshop.wiki.github.org/docs"));
}
}
WebFlux 中的文档
如果项目不是mvc的而是webFlux的,相应的Maven依赖改成下面的即可:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
<version>1.5.12</version>
</dependency>
其他类似。