Spring Boot集成Swagger,自动生成接口文档
Swagger是什么?
Swagger是关于RESTful API设计的一个规范。
最初是2010年设计RESTful API的一个简单的开源规范。 2015年,Swagger项目被Smart Bear Software收购。Swagger规范被捐赠给Linux基金会,并改名为Open API。
OpenAPI 规范(OAS),是定义一个标准的、与具体编程语言无关的RESTful API的规范。OpenAPI 规范使得人类和计算机都能在“不接触任何程序源代码和文档、不监控网络通信”的情况下理解一个服务的作用。如果您在定义您的 API 时做的很好,那么使用 API 的人就能非常轻松地理解您提供的 API 并与之交互了。 如果您遵循 OpenAPI 规范来定义您的 API,那么您就可以用文档生成工具来展示您的 API,用代码生成工具来自动生成各种编程语言的服务器端和客户端的代码,用自动测试工具进行测试等等。
简单来说,如果你按照他规范的方式来设计API(使用json或yaml),那你就可以借助他的工具来生成文档以及前后端的代码,以及进行自动化测试等等。从他供的工具,我们可以大概看出他这一套的流程。
- Swagger Editor:用来编写API规范。(开源)
- Swagger UI:用于展示API规范。(开源)
- Swagger Codegen:用于通过API规范生成服务端和客户端代码。(开源)
- Swagger Hub:云服务,相当于Editor + Codegen + UI。(非开源)
- Swagger Inspector:手动测试API的工具。(非开源)
- SoapUI Pro:功能测试和安全测试的自动化工具。(非开源)
- LoadUI Pro:压力测试和性能测试的自动化工具。(非开源)
目前最新的版本为Swagger3,也即OpenAPI 3。但国内开发者大多数使用的仍是Swagger2。
Swagger提供了基于JAX-RS的Java实现。JAX-RS是基于RESTful 的一套Java规范。。 然而,我们平时使用的Spring并未遵循这套规范,所以,官方提供的实现不能直接用于Spring项目。 于是,有人基于JAX-RS的Java实现,进行的Spring的封装,即SprinFox和SpringDoc。 我们平时说的Swagger2其实指的是springfox-swagger2
集成SpringFox
依赖
这里使用最新版本的依赖
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入 Swagger 依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- validation支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
常用注解说明
- @Api 注解,添加在 Controller 类上,标记它作为 Swagger 文档资源。
- 其中,常用的属性是tags,用于对接口进行分组,可配置多个。如果两个Controller使用同一个tags,则会分组同一组。
在上面的代码中,我将TeacherController的tags配置了多个值,他就出现在了多个分组中。
- @ApiOperation 注解,添加在 Controller 方法上,标记它是一个 API 操作。
- 常用的属性有:value,声明API的名;notes,添加描述。
- 该注解也有tags属性,作用与@Api 注解的tags相同。
- @ApiParam 使用在方法上或者参数上,为操作参数添加额外的元数据。
- 常用的属性有:value,声明参数的名称;required,申明是否必传。
- 声明参数时,还有另外一个注解,@ApiImplicitParam。这个注解也是用来修饰参数的,因为我们接入Swagger2,只是用于文档显示,一般的场景也就是给参数加个说明和必传标识,所以@ApiImplicitParam和@ApiParam 都能满足需求。
- @ApiModel 注解,添加在 POJO 类,声明 POJO 类的信息。
- 常用属性有:value、description
- @ApiModelProperty 注解,添加在 Model 类的成员变量上,声明每个成员变量的信息。
- 常用属性有:value、required
- 值得一提的是,同时至此
- @ApiResponse 注解,添加在 Controller 类的方法上,声明每个响应参数的信息。大多数时候,我我们返回的数据都是一个Model,所以一般不会用这个注解。
另外,sprinfox支持validation注解,可以根据@NotNull、@NotBlank等注解,来自动确定属性是否必传。
在上面的代码中,我将name和studentNumber用@NotBlank标记,在生成的文档中,这两个属性自动识别为必传。
使用
@Data
@ApiModel(value = "学生信息")
public class StudentVo {
@ApiModelProperty(value = "id")
private String id;
@NotBlank(message = "姓名不能为空!")
@ApiModelProperty(value = "名称")
private String name;
@NotBlank(message = "学号不能为空!")
@ApiModelProperty(value = "学号")
private String studentNumber;
}
@RestController
@RequestMapping("/student")
@Api(tags = "人员信息")
public class StudentController {
List<StudentVo> studentVos = new ArrayList<>();
@PostMapping("/add")
@ApiOperation(value = "添加学生")
public String add(
@ApiParam(value = "学生信息", required = true) StudentVo studentVo) {
studentVos.add(studentVo);
return "ok";
}
@GetMapping("get")
@ApiOperation(value = "查询学生")
public StudentVo get(
@ApiParam(value = "学生id", required = true, example = "1", type = "Integer") String id) {
for (StudentVo studentVo : studentVos) {
if (studentVo.getId().equals(id)) {
return studentVo;
}
}
return null;
}
@DeleteMapping("del")
@ApiOperation(value = "删除学生")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "学生id呀", paramType = "query", dataTypeClass = Integer.class, required = true, example = "1024")
})
public String del(
String id) {
Iterator<StudentVo> iterator = studentVos.iterator();
while (iterator.hasNext()) {
StudentVo studentVo = iterator.next();
if (studentVo.getId().equals(id)) {
iterator.remove();
return "ok";
}
}
return "学生不存在!";
}
}
@RestController
@RequestMapping("/teacher")
@Api(tags = {"教师信息", "人员信息"})
public class TeacherController {
@GetMapping("get")
@ApiOperation(value = "查询教师")
public String get(
@ApiParam(value = "教师id") String id) {
return "教师信息";
}
}
项目启动后,打开http://127.0.0.1:8080/swagger-ui/ 即可访问 。
配置分组
可以根据包名或路径,配置不同的分组:
@Configuration
@EnableSwagger2
public class Swagger2Configuration {
@Bean(value = "dockerBean")
public Docket dockerBean() {
//指定使用Swagger2规范
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("标题:api分组1")
//描述字段支持Markdown语法
.description(
"> 支持Markdown语法")
// 服务条款url
.termsOfServiceUrl("https://doc.xiaominfo.com/")
// 联系信息
.contact(new Contact("yefeng", "https://doc.xiaominfo.com/", "yefengr@qq.com"))
.license("此API的许可证信息")
.licenseUrl("https://doc.xiaominfo.com/")
.version("1.0")
.build())
//分组名称
.groupName("分组1")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("pers.h.controller"))
.paths(PathSelectors.ant("/student/**"))
.build();
return docket;
}
@Bean(value = "dockerBean2")
public Docket dockerBean2() {
//指定使用Swagger2规范
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("标题:api分组2")
//描述字段支持Markdown语法
.description("# 描述信息 " +
"> 支持Markdown语法")
// 服务条款url
.termsOfServiceUrl("https://doc.xiaominfo.com/")
// 联系信息
.contact(new Contact("yefeng", "https://doc.xiaominfo.com/", "yefengr@qq.com"))
.license("此API的许可证信息")
.licenseUrl("https://doc.xiaominfo.com/")
.version("1.0")
.build())
//分组名称
.groupName("分组2")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("pers.h.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
效果:
导出静态文档
将swagger文档转换为静态文件,需要借助另外一个工具来完成:Swagger2Markup Swagger2Markup是Github上的一个开源项目。该项目主要用来将Swagger自动生成的文档转换成几种流行的格式以便于静态部署和使用,比如:AsciiDoc、Markdown、Confluence。
依赖
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>jcenter-releases</id>
<name>jcenter</name>
<url>https://jcenter.bintray.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup</artifactId>
<version>1.3.3</version>
</dependency>
<dependencies>
使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class Swagger2MarkupTest {
@Test
public void generateMarkdownFile() throws Exception {
Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
.withMarkupLanguage(MarkupLanguage.MARKDOWN)
.build();
// 目前我使用的是1.3.3,并非最新版本,好像不支持v3的地址
URL apiUrl = new URL("http://localhost:8080/v2/api-docs");
// 指定文件名称
String markdownFileName = "src/docs/markdown/generated/SwaggerApi";
Swagger2MarkupConverter.from(apiUrl)
.withConfig(config)
.build()
//指定生成目录下生成指定文件
.toFile(Paths.get(markdownFileName));
}
}
生成文档:
问题
Failed to start bean 'documentationPluginsBootstrapper'
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.23.jar:5.3.23]
at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_333]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.23.jar:5.3.23]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.5.jar:2.7.5]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) [spring-boot-2.7.5.jar:2.7.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.5.jar:2.7.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-2.7.5.jar:2.7.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.5.jar:2.7.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.5.jar:2.7.5]
at pers.h.swaggerdemo.Application.main(Application.java:10) [classes/:na]
原因:因为Springfox 使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X以上使用的是PathPatternMatcher。 解决方案:修改配置
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
参考文档:blog.csdn.net/mapboo/arti…
集成SpringDoc
SpringFox很优秀,但已经两年没有更新了。有些时候,会出现一些无法解决的问题,寻找同类替代品就很有必有。 SpringDoc更新及时,基于OpenAPI 3 ,是一个更好的选择。 另外,SpringDoc不仅支持Spring WebMvc项目,还可以支持Spring WebFlux项目,甚至Spring Rest和Spring Native项目。 官方文档:
依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.13</version>
</dependency>
注解
对比SpringFox:
- @Api→@Tag
- @ApiIgnore→@Parameter(hidden = true)或@Operation(hidden = true)或@Hidden
- @ApiImplicitParam→@Parameter
- @ApiImplicitParams→@Parameters
- @ApiModel→@Schema
- @ApiModelProperty(hidden = true)→@Schema(accessMode = READ_ONLY)
- @ApiModelProperty→@Schema
- @ApiOperation(value = "foo", notes = "bar")→@Operation(summary = "foo", description = "bar")
- @ApiParam→@Parameter
- @ApiResponse(code = 404, message = "foo")→@ApiResponse(responseCode = "404", description = "foo")
使用
@Data
@Schema(name = "学生信息")
public class StudentVo {
@Schema(name = "id")
private String id;
@NotBlank(message = "姓名不能为空!")
@Schema(name = "名称")
private String name;
@NotBlank(message = "学号不能为空!")
@Schema(name = "学号")
private String studentNumber;
}
@RestController
@RequestMapping("/student")
@Tag(name = "人员信息", description = "人员信息相关接口")
public class StudentController {
List<StudentVo> studentVos = new ArrayList<>();
@PostMapping("/add")
@Operation(summary = "添加学生")
public String add(
@Parameter(name = "学生信息", required = true) StudentVo studentVo) {
studentVos.add(studentVo);
return "ok";
}
@GetMapping("get")
@Operation(summary = "查询学生")
public StudentVo get(
@Parameter(name = "学生id", required = true, example = "1") String id) {
for (StudentVo studentVo : studentVos) {
if (studentVo.getId().equals(id)) {
return studentVo;
}
}
return null;
}
@DeleteMapping("del")
@Operation(summary = "删除学生")
public String del(
@Parameter(name = "学生id", required = true, example = "1") String id) {
Iterator<StudentVo> iterator = studentVos.iterator();
while (iterator.hasNext()) {
StudentVo studentVo = iterator.next();
if (studentVo.getId().equals(id)) {
iterator.remove();
return "ok";
}
}
return "学生不存在!";
}
}
@Configuration
public class SpringDocConfiguration {
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("springdoc 生成的文档")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}
/**
* 如果你只有一个 GroupedOpenApi 删除它并添加属性到你的application.properties:
* <p>
* springdoc.packagesToScan=package1, package2
* springdoc.pathsToMatch=/v1, /api/balance/**
*/
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("分组1")
.pathsToMatch("/**")
.packagesToScan("pers.h.controller")
.build();
}
@Bean
public GroupedOpenApi publicApi2() {
return GroupedOpenApi.builder()
.group("分组2")
.pathsToMatch("/teacher/**")
.build();
}
}
访问地址:http://localhost:8080/swagger-ui/index.html
Knife4j
Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案。 提供了更好看的UI,支持SpringFox和SpringDoc。同样也支持在线测试接口。
结合Springfox使用
依赖:
<!-- Knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<!--使用Swagger2-->
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
配置:
@Configuration
@EnableSwagger2
public class Knife4jConfiguration {
@Bean(value = "dockerBean")
public Docket dockerBean() {
//指定使用Swagger2规范
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
//描述字段支持Markdown语法
.description("# Knife4j RESTful APIs")
.termsOfServiceUrl("https://doc.xiaominfo.com/")
.contact(new Contact("yefeng","https://doc.xiaominfo.com/","yefengr@qq.com"))
.version("1.0")
.build())
//分组名称
.groupName("swaggerDemo")
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("pers.h.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
浏览器访问:http://localhost:8080/doc.htm
结合SpringDoc使用
引入依赖即可:
<!--引入Knife4j的官方ui包,OpenAPI3建议使用springdoc-openapi项目-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-springdoc-ui</artifactId>
<version>3.0.3</version>
</dependency>
聚合多个在线文档
在微服务场景下,每个服务都接入swagger,就会存在多个在线文档,这样对于前后端的对接工作肯定就不友好了。这时候就需要做聚合处理了。 Knife4j的开发者提供了一个微服务聚合中间件,专门用于这类场景: 该中间件支持四种方式实现聚合:
- 基于本地静态JSON文件的方式聚合OpenAPI
- 基于云端HTTP接口的方式聚合
- 基于Eureka注册中心的方式聚合
- 基于Nacos注册中心的方式聚合
这里试试第二种方式:
- 添加依赖
<!-- 聚合多个服务 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-aggregation-spring-boot-starter</artifactId>
<version>2.0.9</version>
</dependency>
- 修改配置
knife4j:
enableAggregation: true
cloud:
enable: true
routes:
- name: 测试分组1
uri: localhost:8080
location: /v2/api-docs?group=swaggerDemo
- name: 测试分组2
uri: localhost:8081
location: /v2/api-docs?group=swaggerDemo
- 效果
参考文章
www.iocoder.cn/Spring-Boot… www.iocoder.cn/Spring-Boot… blog.csdn.net/m0_37899908… doc.xiaominfo.com/docs/middle… blog.csdn.net/zhenghongcs…
转载自:https://juejin.cn/post/7172447982548156429