SpringDoc【使用详解】
SpringDoc使用详解
- 一、何为SpringDoc
- 二、概念解释
- 三、SpringDoc使用
- 2.1简单集成
- 2.2 配置SpringDoc
- 2.2.1 yml方式配置
- 2.2.2配置文档信息
- 2.3配置文档分组
- 2.4使用注解
- 2.4.1 @Tag
- 2.4.2 @Operation
- 2.4.3 @Schema
- 2.4.4 @NotNull
- 2.4.5 @Parameter
- 2.4.6 @Parameters
- 2.4.7 @ApiResponses 和@ApiResponse
- 四、认证授权
- 3.1无需认证
- 3.2需要认证
- 五、SpringDoc整合Knife4j
- 1.前世今生
- 2.Spring Boot版本兼容性
- 3.Spring Boot 3
- 4.Spring Boot 2
一、何为SpringDoc
SpringDoc是一个用来自动生成API文档的库。它是基于SpringBoot项目的,遵循OpenAPI3(一个组织规定的规范)规范。它是通过检查我们运行中的程序,推断出基于Spring配置、类结构和各种注解的API语义,从而自动生成JSON、YAML和HTML格式的接口文档。
而我们不得不提的就是Swagger。Swagger是一个公司的开源项目,将自己的API设计贡献给了OpenAPI并由其标准化。在SpringDoc之前我们还可以使用Springfox,和SpringDoc一样是一个用于生成API文档的库,2020年起不再更新。
SpringDoc官网
二、概念解释
谈到API文档,那就绕不开大名鼎鼎的Swagger,但是你是否还听说过:OpenAPI,Springfox,Springdoc?你第一次看到这些脑瓜子是不是嗡嗡的?
-
OpenAPI
是一个组织(OpenAPI Initiative),他们指定了一个如何描述HTTP API的规范(OpenAPI Specification)。既然是规范,那么谁想实现都可以,只要符合规范即可。 -
Swagger
它是SmartBear这个公司的一个开源项目,里面提供了一系列工具,包括著名的 swagger-ui。swagger是早于OpenApi的,某一天swagger将自己的API设计贡献给了OpenApi,然后由其标准化了。 -
Springfox
是Spring生态的一个开源库,是Swagger与OpenApi规范的具体实现。我们使用它就可以在spring中生成API文档。以前基本上是行业标准,目前最新版本可以支持 Swagger2, Swagger3 以及 OpenAPI3 三种格式。但是其从 2020年7月14号就不再更新了,不支持springboot3,所以业界都在不断的转向我们今天要谈论的另一个库Springdoc,新项目就不要用了。 -
Springdoc
算是后起之秀,带着继任Springfox的使命而来。其支持OpenApi规范,支持Springboot3,我们的新项目都应该使用这个。
三、SpringDoc使用
我们可以在springboot中使用SpringDoc来生成API文档,详情可以参考官网,下面我们来简单的实践一下。
2.1简单集成
在springboot中使用springdoc起步非常容易,只需要引入其starter即可
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
运行后访问下面的链接即可
http://server:port/context-path/swagger-ui.html
例如
http://localhost:8080/swagger-ui.html
例如我们有如下代码:
@RestController
@RequestMapping("/api/programmer")
public class ProgrammerController {
@PostMapping()
public Programmer createProgrammer(@RequestBody CreateProgrammerRequest request) {
return new Programmer(1, request.getAge(), request.getProgrammingLang());
}
@GetMapping("/{id}")
public Programmer getProgrammer(@PathVariable Integer id) {
return new Programmer(1, 35, List.of("Java,Python,SQL"));
}
}
添加依赖运行后访问http://localhost:8080/swagger-ui.html后结果如下
是不是特牛逼?当然springdoc的集成不可能就这点东西,不然也没有这篇文章了,咱接着往下看。苦于网上的一些教程不直观,对初学者不友好,所以本文以代码和其效果的形式来展示,保你一看就会。
2.2 配置SpringDoc
2.2.1 yml方式配置
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
swagger-ui:
# 持久化认证数据
persistAuthorization: true
info:
# 标题
title: '标题:${demo.name}多租户管理系统_接口文档'
# 描述
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
# 版本
version: '版本号: ${demo.version}'
# 作者信息
contact:
name: Agazigi
email: 123456789@qq.com
url: www.baidu.com
components:
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
#这里定义了两个分组,可定义多个,也可以不定义
group-configs:
- group: 1.演示模块
packages-to-scan: org.dromara.demo
- group: 2.通用模块
packages-to-scan: org.dromara.web
- group: 3.系统模块
packages-to-scan: org.dromara.system
- group: 4.代码生成模块
packages-to-scan: org.dromara.generator
2.2.2配置文档信息
得益于springboot的强大,我们只需添加一个依赖就可以使用API文档了,但是使用的都是默认值,我们当然也希望对其进行各种自定义的配置
- 配置文档信息
创建一个OpenAPI 的bean,配置文档名称等信息
@Configuration
public class SpringDocConfig {
@Bean
public OpenAPI myOpenAPI() {
return new OpenAPI()
.info(new Info() //设置信息
.title("程序员API") //标题
.description("程序员的大本营") //描述信息
.version("v1.0.0") //版本
.license(new License() //许可证
.name("许可协议")
.url("https://shusheng007.top"))
.contact(new Contact() //作者信息
.name("书生007")
.email("wangben850115@gmail.com")))
.externalDocs(new ExternalDocumentation() //外部文档
.description("ShuSheng007博客")
.url("https://shusheng007.top"));
}
// 指定扫描的包路径
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("my_api") // 分组名称(自定义)
.packagesToScan("com.ls.ai.demo.controller") // 包路径
.addOpenApiMethodFilter(methodPredicate()) // 方法级注解过滤
.build();
}
// 定义过滤条件:仅包含带有 @Operation 注解的方法
private Predicate<HandlerMethod> methodPredicate() {
return handlerMethod ->
handlerMethod.hasMethodAnnotation(io.swagger.v3.oas.annotations.Operation.class);
}
// 可选:过滤类级别的注解(如 @Tag)
private Predicate<HandlerMethod> classPredicate() {
return handlerMethod ->
handlerMethod.getBeanType().isAnnotationPresent(io.swagger.v3.oas.annotations.tags.Tag.class);
}
}
效果:
里面的配置项很多,可以根据代码和截图对照一下,按照自己的需求配置即可
2.3配置文档分组
用来配置分组的,假如你有两类controller一类以/api为前缀,一类以/admin为前缀,就可以将其配置为两个分组。很多时候我们只有一个分组,所以就不需要下面的配置。
@Configuration
public class SpringDocConfig {
...
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("api")
.pathsToMatch("/api/**")
.build();
}
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/admin/**")
.build();
}
}
效果:
可以通过右上角的下拉框选择要展示的group。
2.4使用注解
这个是咱们的重头戏,OpenApi
规范提供了很多注解,下面是一些常用的
注解 | 含义 |
---|---|
@Tag | 用在controller类上,描述此controller的信息 |
@Operation | 用在controller的方法里,描述此api的信息 |
@Parameter | 用在controller方法里的参数上,描述参数信息 |
@Parameters | 用在controller方法里的参数上 |
@Schema | 用于Entity,以及Entity的属性上 |
@ApiResponse | 用在controller方法的返回值上 |
@ApiResponses | 用在controller方法的返回值上 |
@Hidden | 用在各种地方,用于隐藏其api |
下面我们一起来看看效果
2.4.1 @Tag
@Tag(name = "程序员", description = "程序员乐园")
@RestController
@RequestMapping("/api/programmer")
public class ProgrammerController {
...
}
效果
2.4.2 @Operation
@Operation(summary = "创建程序员", description = "用于创建一个闷骚的程序员")
@PostMapping()
public Programmer createProgrammer(@RequestBody CreateProgrammerRequest request) {
return new Programmer(666, "王二狗", request.getAge(), request.getProgrammingLang());
}
@Operation
其实很复杂的,我们可以将下面要用的@Parameter
以及@ApiResponse
都可以配置在它里面。
2.4.3 @Schema
@Schema
用于实体类和其属性,例如有如下代码:
@Schema(description = "创建程序员入参")
public class CreateProgrammerRequest {
@Schema(description = "名称", example = "王二狗")
private String name;
@Schema(description = "年龄", example = "35")
private Integer age;
@Schema(description = "掌握的编程语言", type = "List", example = "[\"Java\",\"Sql\"]")
private List<String> programmingLang;
}
效果:
还可以切换到 Schema选项进行查看
2.4.4 @NotNull
同时,Springdoc还支持 Java Bean Validation API 的注解,例如@NotNull
等
@Schema(description = "创建程序员入参")
public class CreateProgrammerRequest {
@NotNull
@Schema(description = "名称", example = "王二狗")
private String name;
@NotNull
@Min(18)
@Max(35)
@Schema(description = "年龄", example = "35")
private Integer age;
...
}
效果:
注意红框中的内容,name和age右上角都有出现了一个红色的星星,表示是必填的。age也被限制了范围。
2.4.5 @Parameter
用于添加接口参数信息
@GetMapping("/{id}")
public Programmer getProgrammer(@Parameter(description = "程序员id") @PathVariable Integer id) {
...
}
效果
2.4.6 @Parameters
与@Parameter
作用一样,但是可以批量添加,不用一个一个的写在参数前面
@Parameters(value = {
@Parameter(name = "name", description = "姓名", in = ParameterIn.PATH),
@Parameter(name = "age", description = "年龄", in = ParameterIn.QUERY)
})
@GetMapping("/{name}")
public List<Programmer> getProgrammers(@PathVariable("name") String name, @RequestParam("age") Integer age) {
...
}
parameters里的parameter使用name来找到方法中的入参,这块要对应上。
效果:
2.4.7 @ApiResponses 和@ApiResponse
顾名思义,此注解用来描述返回值的。
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功",
content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = Programmer.class))}),
@ApiResponse(responseCode = "405", description = "非法输入",
content = @Content)
})
@PostMapping()
public Programmer createProgrammer(@RequestBody CreateProgrammerRequest request) {
...
}
效果
可见,我们成功配置了两种情况的返回值。但是我们一般不会手动给每个API 写上一堆@ApiResponse
,那的多烦啊。业界通常会有一个统一的返回类型,例如
public class Result<T> implements Serializable {
private int code;
private String message;
private T data;
private String traceId;
}
还会有一个统一的异常处理类,使用@RestControllerAdvice
标记,然后每个方法会捕捉对应的异常,只要我们使用@ResponseStatus
来标记这些方法,springdoc就会自动生成相应的文档
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handleException(HttpServletRequest httpServletRequest, Exception e) {
return new Result(StatusCode.FAILED.getCode(), StatusCode.FAILED.getMessage(), null);
}
@ExceptionHandler(value = ApiException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result handleBusinessException(HttpServletRequest httpServletRequest, ApiException e) {
return new Result(e.getCode(), e.getMessage(), null);
}
}
例如我们有如下代码:
...
@RequestMapping(value = "/api/programmer",produces = "application/json")
public class ProgrammerController {
@GetMapping("/{id}")
public Result<Programmer> getProgrammer(@Parameter(description = "程序员id") @PathVariable Integer id) {
...
}
}
生成的api文档如下:
可见,多了400和500。
四、认证授权
我们知道·swagger-ui·不仅可以看API文档也可以直接调用API,这点很牛逼。但有时我们的服务需要认证,否则就调用不通,那怎么办?稍安勿躁,OpenApi规范也考虑到了这个问题。
OpenAPI 3.0 支持下面的认证模式:
- HTTP authentication schemes (使用Authorization header):
- Basic
- Bearer
- Other HTTP schemes as defined by RFC 7235 and HTTP Authentication Scheme Registry
- API keys in headers, query string or cookies
- Cookie authentication
- OAuth 2
- OpenID Connect Discovery
有的我也没用过,我们常用的就是Http Auth,以及OAuth2。本文以HTTP Bearer来作为我们服务的认证授权模式,如下图所示。
3.1无需认证
当你的服务没有认证机制的话是可以直接调用的:
每个API 右上角都有一个try it out按钮,点击输入参数点击execute按钮即可,如下所示。
3.2需要认证
如果你的服务需要认证后才能调用,那么默认情况下就不行了。例如你使用了Spring Security,或者你自己写了个Filter 来实现认证功能。
下面是demo服务用来做认证的Filter,采用HTTP Bearer 模式。所以需要在请求的Authentication Header 里 携带token 123才能通过认证。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
...
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
...
// get token from header [Authorization: Bearer <token>]
String authHeader = request.getHeader(AUTH_HEADER);
...
String authToken = authHeader.split(" ")[1];
if (!"123".equals(authToken)) {
genUnauthResponse(response);
return;
}
filterChain.doFilter(request, response);
}
所以当从swagger-ui
上调用API时,返回了401,如下图所示。那怎么才能正常调用API呢?接下来让我们看一下
Springdoc 使用@SecurityScheme
来定义一个安全模式,我们可以定义全局的,也可以针对某个controller定义类级别的,我们这里定义一个全局的。
- 在Application类上添加
@SecurityScheme
@SecurityScheme(name = "api_token", type = SecuritySchemeType.HTTP, scheme ="bearer", in = SecuritySchemeIn.HEADER)
@SpringBootApplication
public class SpringdocIntegrateApplication {
public static void main(String[] args) {
SpringApplication.run(SpringdocIntegrateApplication.class, args);
}
}
我们定义了一个名为api_token
的安全模式,并指定了其使用HTTP Bearer的方式。
- 使此安全模式生效
@Configuration
public class SpringDocConfig {
@Bean
public OpenAPI myOpenAPI() {
return new OpenAPI()
...
.security(List.of(new SecurityRequirement().addList("api_token")));
}
}
注意api_token
正是我们第一步定义的那个Security schema。
经过上面两步后查看生成的API文档,你会发现其右上角出现了一个Authorize
的按钮, 而且每个API的右边也出现了一把小锁,如下图所示。
当点击Authorize
按钮后会弹出一个让你提供认证信息的弹出,其根据不同的认证方式会有所区别。
我们这里需要输入一个token,然后点击Authorize按钮后关闭此弹窗。你会发现每个API右边的那把小锁从打开状态变为了关闭状态,颜色也从灰色变成了黑色,证明其生效了。
然后我们再来请求一下API,就会正常返回了。
- 声明是否需要认证
默认情况下按照上面两步设置后,整个应用程序的API生效,但是有的API是不需要认证的,例如登录。 对于这种情况,我们可以使用@SecurityRequirements()
来设置。
@RestController
@RequestMapping(value = "/admin",produces = "application/json")
public class AuthController {
...
@SecurityRequirements()
@PostMapping("/login")
public Result<String> login(@RequestBody LoginRequest request){
return Result.ok("123");
}
}
@SecurityRequirements()
里面需要一个String数组,里面列出需要使用的@SecurityScheme
,例如我们这里的api_token
。如果不写就说明不需要任何的安全模式,这里就是这种情况。
从上图可以看出,/admin/login
这个API右边没有小锁的标志,表示其不需要认证。针对本案例来说,不需要认证的意思就是:在发起这个请求的时候,不在Authentication header里面附加token。
这里只是展示了HTTP bearer 这种安全模式,其他的原理类似,小朋友们可以开动你们聪明的大脑研究一下,给补充到这里。
五、SpringDoc整合Knife4j
1.前世今生
在更名为Knife4j
之前,原来的名称是叫swagger-bootstrap-ui
,这是两种不一样风格的Ui,对比情况如下:
名称 | 开发语言&框架 | 状态 | 最后版本 | 风格 |
---|---|---|---|---|
Knife4j | Java、JavaScript、Vue | 持续更新中… | 无 | 黑色 |
swagger-bootstrap-ui | Java、JavaScript、jQuery | 停更 | 1.9.6 | 蓝色 |
Knife4j
从开源至今,目前主要经历版本的变化,分别如下:
版本 | 说明 |
---|---|
1.0~1.9.6 | 名称是叫swagger-bootstrap-ui,蓝色风格Ui |
1.9.6 | 蓝色皮肤风格,开始更名,增加更多后端模块 |
2.0~2.0.5 | Ui基于Vue2.0+AntdV重写,黑色风格,参考示例,底层依赖的springfox框架版本是2.9.2,仅提供Swagger2规范的适配 |
2.0.6~2.0.9 | 底层springfox框架版本升级至2.10.5,仅提供Swagger2规范的适配 |
3.0~3.0.3 | 底层依赖springfox框架版本升级至3.0.3,OpenAPI规范是v3,过度版本,建议开发者不要使用 |
4.0~ | 区分OpenAPI2和Swagger3的Maven坐标artifactId OpenAPI2规范服务端解析框架稳定在springfox2.10.5 OpenAPI3框架服务端解析跟随springdoc项目更新迭代 建议开发者使用该版本,请参考4.x升级文档 |
2.Spring Boot版本兼容性
首先,确保您了解您的项目所使用的Spring Boot版本。
以下是一些常见的Spring Boot版本及其对应的Knife4j版本兼容推荐:
Spring Boot版本 | Knife4j Swagger2规范 | Knife4j OpenAPI3规范 |
---|---|---|
1.5.x~2.0.0 | <Knife4j 2.0.0 | >=Knife4j 4.0.0 |
2.0~2.2 | Knife4j 2.0.0 ~ 2.0.6 | >=Knife4j 4.0.0 |
2.2.x~2.4.0 | Knife4j 2.0.6 ~ 2.0.9 | >=Knife4j 4.0.0 |
2.4.0~2.7.x | >=Knife4j 4.0.0 | >=Knife4j 4.0.0 |
>= 3.0 | >=Knife4j 4.0.0 | >=Knife4j 4.0.0 |
Knife4j在之前的版本更新中,逐渐提供了一些服务端适配的增强特性功能。
但是开发者应该明白,不管是Swagger2规范还是OpenAPI3规范,Knife4j的最新版本的纯Ui版本,是可以适配Spring Boot所有版本的。
如果你不考虑使用Knife4j提供的服务端增强功能,引入Knife4j的纯Ui版本没有任何限制。只需要考虑不同的规范即可
3.Spring Boot 3
提示
Spring Boot 3 只支持OpenAPI3规范
Knife4j提供的starter已经引用springdoc-openapi的jar,开发者需注意避免jar包冲突
JDK版本必须 >= 17
详细Demo请参考knife4j-spring-boot3-demo
首先,引用Knife4j的starter,Maven坐标如下:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
其实现在就可以使用Knife4j了,暂不做其他配置,启动项目,浏览器输入http://localhost:8080/doc.html
查看接口文档
由于我们没有进行任何的属性配置,所以看到的页面是knife4j的初始页面
引入之后,其余的配置,开发者即可完全参考springdoc-openapi的项目说明,Knife4j只提供了增强部分,如果要启用Knife4j的增强功能,可以在配置文件中进行开启
部分配置如下:
# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.xiaominfo.knife4j.demo.web
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
Knife4j更多增强配置明细,请移步文档进行查看
最后,使用OpenAPI3的规范注解,注释各个Spring的REST接口,示例代码如下:
@RestController
@RequestMapping("body")
@Tag(name = "body参数")
public class BodyController {
@Operation(summary = "普通body请求")
@PostMapping("/body")
public ResponseEntity<FileResp> body(@RequestBody FileResp fileResp){
return ResponseEntity.ok(fileResp);
}
@Operation(summary = "普通body请求+Param+Header+Path")
@Parameters({
@Parameter(name = "id",description = "文件id",in = ParameterIn.PATH),
@Parameter(name = "token",description = "请求token",required = true,in = ParameterIn.HEADER),
@Parameter(name = "name",description = "文件名称",required = true,in=ParameterIn.QUERY)
})
@PostMapping("/bodyParamHeaderPath/{id}")
public ResponseEntity<FileResp> bodyParamHeaderPath(@PathVariable("id") String id,@RequestHeader("token") String token, @RequestParam("name")String name,@RequestBody FileResp fileResp){
fileResp.setName(fileResp.getName()+",receiveName:"+name+",token:"+token+",pathID:"+id);
return ResponseEntity.ok(fileResp);
}
}
最后,访问Knife4j的文档地址:http://ip:port/doc.html
即可查看文档
4.Spring Boot 2
提示
Spring Boot 版本建议 2.4.0~3.0.0之间
Spring Boot 版本 < 2.4 版本则建议选择Knife4j 4.0之前的版本
Spring Boot 2 + OpenAPI2 demo:knife4j-spring-boot27-demo
Spring Boot 2 + OpenAPI3 demo:knife4j-springdoc-openapi-demo
OpenAPI2
OpenAPI2(Swagger)规范是Knife4j之前一直提供支持的版本,底层依赖框架为Springfox,此次在4.0版本开始
Knife4j有了新的变化,主要有以下几点:
- Springfox版本选择的依然是2.10.5版本,而并非springfox最新3.0.0版本
- 不支持以Springfox框架为基础的OpenAPI3规范,放弃Springfox项目的后续版本适配支持工作
- Spring Boot 版本建议 2.4.0~3.0.0之间
可以使用 knife4j-openapi2-spring-boot-starter,maven 坐标如下:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
配置yml属性,如下:
knife4j:
enable: true
openapi:
title: Knife4j官方文档
description: "`我是测试`,**你知道吗**
# aaa"
email: xiaoymin@foxmail.com
concat: 八一菜刀
url: https://docs.xiaominfo.com
version: v4.0
license: Apache 2.0
license-url: https://stackoverflow.com/
terms-of-service-url: https://stackoverflow.com/
group:
test1:
group-name: 分组名称
api-rule: package
api-rule-resources:
- com.knife4j.demo.new3
最后,访问Knife4j的文档地址:http://ip:port/doc.html
即可查看文档
OpenAPI3
OpenAPI3的规范,目前针对Java的Spring Boot项目,主要支持的有2个版本
- springfox 3.0.0: 同时兼容OpenAPI2以及OpenAPI3,但是停更很久了
- springdoc-openapi: 兼容OpenAPI3规范,更新速度频繁
Knife4j在只有的OpenAPI3规范中,底层基础框架选择springdoc-openapi项目
针对Springfox3.0.0版本会放弃。
建议开发者如果使用OpenAPI3规范的话,也尽快迁移过来。
可以使用 knife4j-openapi3-spring-boot-starter,maven 坐标如下:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
引入jar包后,同上面的Spring Boot 3版本使用方式一样,进行配置即可。