
Swagger API 项目最初由 Wordnik 的技术联合创始人 Tony Tam 于 2011 年创建,主要针对在 Wordnik 的产品开发过程中 API 文档自动化和客户端 SDK 生成的需求。设计师/开发人员 Zeke Sikelianos 创建了 Swagger 这个名字,Swagger API 项目于 2011 年 9 月开源。
2015 年 11 月,维护 Swagger 的公司 SmartBear Software 宣布在 Linux 基金会的赞助下,创建了一个名为 OpenAPI Initiative 的新组织,包括谷歌、IBM 和微软在内的各种公司都是创始成员。
2016 年 1 月 1 日,Swagger 规范更名为 OpenAPI 规范,并移至 GitHub 上。
Swagger 官网上将 Swagger 分为三大块:
其中,Swagger 开源工具可以分成以下三部分:
在 SpringBoot 项目中一般使用 Swagger 用的是 Swagger UI 部分,用于接口的文档在线自动生成和功能测试。 常见的库是使用 springfox 提供的 springfox,不过最新更新在2020年,已经两年没更新,最新的替代品是 springdoc-openapi 。
下面会介绍 springfox swagger 的使用,虽然已经不再维护,但文档齐全,使用的人多,有什么问题可以十分方便找到答案。
不同 swagger 版本选择pom依赖也不同
swagger2
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
swagger3(openapi3) 提供了boot-starter版本,但自此之后再无更新
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
swagger2 ui 访问路径:http://localhost:8080/swagger-ui.html
swagger3 ui 访问路径:http://localhost:8080/swagger-ui/index.html
下面会使用 swagger3 为例,探索 swagger 使用
swagger 以下注解都位于 io.swagger.annotations 包内
| 注解名 | 标记范围 | 含义 |
|---|---|---|
| @Api | TYPE | 用于标注一个 Controller,表明是 swagger 资源 。在默认情况下,Swagger-Core 只会扫描解析具有 @Api 注解的类 |
| @ApiImplicitParam | METHOD | 用于标注 @ApiOperation 操作中的单个参数 |
| @ApiImplicitParams | METHOD | 用于标注 @ApiOperation 操作中的多个参数 |
| @ApiModel | TYPE | 用于标注一个类,表明此类是 swagger 的 Model 类 |
| @ApiModelProperty | METHOD,FIELD | 用于描述被 @ApiModel 标记类的属性 |
| @ApiOperation | METHOD,FIELD | 表明是一个 http 请求的操作 |
| @ApiParam | PARAMETER,METHOD,FIELD | 和 @ApiImplicitParam 类似, 但只能和 JAX-RS 1.x/2.x 注解结合使用 |
| @ApiResponse | METHOD | 用于描述 @ApiOperation 单个响应 |
| @ApiResponses | METHOD | 用于描述 @ApiOperation 多个响应 |
| @Authorization | METHOD | 定义要在资源或操作上使用的授权方案。应该在 @Api 或 @ApiOperation 中使用 |
| @AuthorizationScope | METHOD | 描述 OAuth2 授权范围,应该在 @Authorization 中使用 |
| @Example | ANNOTATION_TYPE | 用于描述一个示例 |
| @ExampleProperty | ANNOTATION_TYPE | 用于描述一个示例属性 |
| @ResponseHeader | METHOD | 定义 @ApiResponse head部分 |
下面注解不在 io.swagger.annotations 包下面
| 注解名 | 标记范围 | 含义 |
|---|---|---|
| @EnableSwagger | TYPE | 启用 swagger 1.2 规范 |
| @EnableSwagger2 | TYPE | 启用 swagger 2.0 规范 |
| @EnableOpenApi | TYPE | 启用 open api 3.0.3 规范 |
| @ApiIgnore | METHOD, TYPE, PARAMETER | 忽略所标记的方法、类或参数,不会在UI界面显示 |
一个 Swagger3 文档例子 UI 如下

可以通过 java 代码对界面进行配置
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.any()) // 可以通过PathSelectors.ant("/a/**")来分隔 进行分组
.build();
}
//生成接口信息,包括标题、联系人、版本等
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3 demo接口文档")
.description("如有疑问,请联系开发工程师。")
.contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
.version("1.0")
.build();
}
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.supportedSubmitMethods(DEFAULT_SUBMIT_METHODS) // 支持的http方法
.build();
}
}
Docket 是 主要的配置类,如果包含分组可以配置多个,上面使用 directModelSubstitute 是为了在前台用 String 接收时间类型
UiConfiguration 中可以配置一些UI界面选项
docExpansion 接口文档展开
operationsSorter http操作排序
defaultModelRendering Model渲染
EXAMPLE 接口 response 默认显示 example

MODEL 接口 response 默认显示 schema

下面以一个选课系统来演示 swagger 使用
课程类
@ApiModel
@Data
public class Course {
@ApiModelProperty(value = "课程ID")
private Long id;
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "类别")
private String category;
@ApiModelProperty(value = "学分")
private String credit;
@ApiModelProperty(value = "学生数量")
private Integer number;
@ApiModelProperty(value = "老师名字")
private List<String> teachers;
@ApiModelProperty(value = "上课时间-星期")
private DayOfWeek timeWeek;
@ApiModelProperty(value = "开始时间")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime startTime;
@ApiModelProperty(value = "结束时间")
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime endTime;
@ApiModelProperty(value = "地点")
private String place;
}
学生类
@ApiModel
@Data
public class Student {
@ApiModelProperty(value = "学号")
private String id;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "专业")
private String major;
@ApiModelProperty(value = "年级")
private CollegeGradeEnum grade;
@ApiModelProperty(value = "性别")
private String gender;
}
学生年级枚举
public enum CollegeGradeEnum {
FRESHMAN,
SOPHOMOREER,
JUNIOR,
SENIOR
}
公共VO
@ApiModel
@Data
@Builder
public class CommonVo<T> {
@ApiModelProperty("状态码")
private String code;
@ApiModelProperty("状态信息")
private String message;
@ApiModelProperty("数据")
private T data;
}
课程Controller
@Api(value = "courseAPI",tags = "课程API")
@RestController
@RequestMapping
public class CourseController {
private static final Logger logger = LoggerFactory.getLogger(CourseController.class);
@ApiOperation("创建一门课程")
@ApiImplicitParam(name = "course", value = "课程信息", dataTypeClass = Course.class, paramType = "body")
@RequestMapping(value = "/createCourse",method = RequestMethod.POST)
public ResponseEntity<CommonVo> createCourse(@RequestBody Course course) {
return ResponseEntity.ok(CommonVo.builder().code("0").message("success").build());
}
@ApiOperation("修改课程信息")
@ApiImplicitParams({
@ApiImplicitParam( name = "id", value = "课程id", paramType = "query", dataTypeClass = Long.class, required = true),
@ApiImplicitParam( name = "category", value = "课程类别", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "name", value = "课程名称", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "timeWeek", value = "星期", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "startTime", value = "上课开始时间", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "endTime", value = "上课结束时间", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "credit", value = "学分", paramType = "query", dataTypeClass = String.class),
@ApiImplicitParam( name = "teachers", value = "老师", paramType = "query", dataTypeClass = String.class, allowMultiple = true),
@ApiImplicitParam( name = "number", value = "上课人数", paramType = "query", dataTypeClass = Integer.class),
@ApiImplicitParam( name = "place", value = "上课地点", paramType = "query", dataTypeClass = String.class),
})
@RequestMapping(value = "/updateCourse",method = RequestMethod.PUT)
public ResponseEntity<CommonVo<Course>> updateCourse(Course course){
logger.info("{}}", course);
return ResponseEntity.ok(CommonVo.<Course>builder().code("200").data(course).build());
}
@ApiOperation("操作课程")
@RequestMapping(value = "/testCourse", method = RequestMethod.POST)
public ResponseEntity<CommonVo<Course>> test(@ApiParam(name = "course") @RequestBody Course course) {
return null;
}
@ApiOperation("查看课程信息")
@RequestMapping(value = "/courseList",method = RequestMethod.GET)
public ResponseEntity<CommonVo<List<Course>>> courseList(@ApiParam(name = "studentId", value = "学号") String studentId) {
return null;
}
@ApiOperation("查看单个课程信息")
@RequestMapping(value = "/course",method = RequestMethod.GET)
public ResponseEntity<CommonVo<Course>> course(@ApiParam(name = "courseId", value = "课程Id") Long courseId) {
return null;
}
@ApiOperation("删除课程")
@RequestMapping(value = "/deleteCourse",method = RequestMethod.DELETE)
public ResponseEntity<CommonVo> deleteCourse(Long courseId) {
return null;
}
}
学生Controller
@Api(value = "student API", tags = "学生API")
@RestController
@RequestMapping("/home")
public class StudentController {
@ApiOperation("新增学生")
@ApiImplicitParam(name = "student", value = "学生信息", dataTypeClass = Student.class, paramType = "body")
@RequestMapping(value = "/createCourse",method = RequestMethod.POST)
public Object createCourse(@RequestBody Student student) {
return null;
}
}
一般通过 @ApiImplicitParam 或 @ApiParam 来定义接收的字段,@ApiImplicitParam 用处比较广,可以接收表单和 json 等各种字段,常用属性如下所示
name
参数名字
value
参数描述
paramType
参数类型,可以为以下部分
header:@RequestHeader
query:@RequestParam
path:@PathVariable
body:@RequestBody
form:表单
dataTypeClass
参数类型,String类型用的比较多,默认 Void.class
required
参数是否必填,默认 false
allowMultiple
是否数组,,默认 false
@ApiImplicitParam

@ApiParam 在官方文档中注明需要和 JAX-RS 1.x/2.x 注解结合使用,实际使用发现可以和 SpringBoot 中注解一起使用,使用效果和 @ApiImplicitParam 类似

字段含义可以通过Scchema 查看
后台返回前端字段,不需要特别指出,只需要使用 @ApiModel @ApiModelProperty将返回数据类定义好,swagger 会自动将 controller 返回值解析为 json 数据.。以上面示例为例,ResponseEntity会自动解析为下面字段

每个字段的含义,可以通过schema标签查看

在UiConfiguration uiConfig配置defaultModelsExpandDepth(-1)
当Controller很多时,页面查找不方便,可以根据路径将各个Controller进行分组
@Bean
public Docket studentApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("学生")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.ant("/student/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
.build();
}
@Bean
public Docket courseApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("课程")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.ant("/course/**")) // 可以通过PathSelectors.ant("/zhao/**")来分隔 进行分组
.build();
}
@Bean
public Docket defaultApi(Environment environment) {
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.OAS_30)
.enable(flag)
.apiInfo(apiInfo())
.groupName("default")
.directModelSubstitute(LocalTime.class, String.class)
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class)
.select()
.apis(RequestHandlerSelectors.basePackage("com.aabond.demoswagger.controller"))
.paths(PathSelectors.any())
.build();
}
//生成接口信息,包括标题、联系人等
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3 demo接口文档")
.description("如有疑问,请联系开发工程师。")
.contact(new Contact("aabond", "http://localhost:8080/swagger-ui/index.html", "aabond@foxmail.com"))
.version("1.0")
.build();
}
swagger 通过 swagger2markup 这个项目将文档转成静态文档。注意:暂时只能生成 swagger2 文档,通过查找源码发现,解析类Swagger20Parser 解析json数据前会判断是否包含 swagger 这个节点,而 swagger3 的 swagger 节点名字已经改成 openapi 了,这个问题暂时未修复,相关问题已经有人提ISSUE Swagger v3 cannot use swagger2markup(1.3.3)
所以需要生成文档,临时的解决方案是将 new Docket(DocumentationType.OAS_30) 修改为 new Docket(DocumentationType.SWAGGER_2)
生成 asciidoc 文档 mvn swagger2markup:convertSwagger2markup
<properties>
<swagger2markup.version>1.3.3swagger2markup.version>
<asciidoctor.input.directory>${project.basedir}/src/docs/asciidocasciidoctor.input.directory>
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/htmlasciidoctor.html.output.directory>
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdfasciidoctor.pdf.output.directory>
properties>
<plugin>
<groupId>io.github.swagger2markupgroupId>
<artifactId>swagger2markup-maven-pluginartifactId>
<version>1.3.3version>
<configuration>
<swaggerInput>http://localhost:8080/v2/api-docsswaggerInput>
<outputDir>${asciidoctor.input.directory}outputDir>
<config>
<swagger2markup.markupLanguage>ASCIIDOCswagger2markup.markupLanguage>
config>
configuration>
plugin>
生成 HTML 和 PDF 文档 mvn test
<plugin>
<groupId>org.asciidoctorgroupId>
<artifactId>asciidoctor-maven-pluginartifactId>
<version>1.5.5version>
<configuration>
<sourceDirectory>${asciidoctor.input.directory}sourceDirectory>
<headerFooter>trueheaderFooter>
<doctype>bookdoctype>
<sourceHighlighter>coderaysourceHighlighter>
<attributes>
<toc>lefttoc>
<toclevels>3toclevels>
<sectnums>truesectnums>
attributes>
configuration>
<dependencies>
<dependency>
<groupId>org.asciidoctorgroupId>
<artifactId>asciidoctorj-pdfartifactId>
<version>1.5.0-alpha.16version>
dependency>
<dependency>
<groupId>org.jrubygroupId>
<artifactId>jruby-completeartifactId>
<version>1.7.21version>
dependency>
dependencies>
<executions>
<execution>
<id>output-htmlid>
<phase>testphase>
<goals>
<goal>process-asciidocgoal>
goals>
<configuration>
<backend>html5backend>
<outputDirectory>${asciidoctor.html.output.directory}outputDirectory>
configuration>
execution>
<execution>
<id>output-pdfid>
<phase>testphase>
<goals>
<goal>process-asciidocgoal>
goals>
<configuration>
<backend>pdfbackend>
<outputDirectory>${asciidoctor.pdf.output.directory}outputDirectory>
configuration>
execution>
executions>
plugin>
最终在 target 目录下生成

PDF样式如下,可以发现中文有丢失情况,解决方法:swagger+asciidoctor 导出PDF中文缺失乱码问题解决

推荐一个第三方主题,访问路径为/doc.html
<dependency>
<groupId>com.github.xiaoymingroupId>
<artifactId>knife4j-spring-uiartifactId>
<version>3.0.3version>
dependency>

3.0版本的各种顺序默认都变成了字母顺序,而且不好更改,是个大坑。介意这个问题的可以考虑换成 springdoc-openapi
默认字母顺序,理论上讲可以通过 tagsSorter 这个方法修改,但是 TagsSorter 这个枚举也只有 ALPHA(“alpha”) 一个值,所以说还是不能修改
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.tagsSorter(TagsSorter.ALPHA)
.build();
}
@ApiOperation 中 position 也已经被废弃,但是可以通过 operationsSorter 来进行更改, OperationsSorter 有两种配置,一种是 ALPHA (字母顺序),另一种是 METHOD (根据HTTP方法排序)。这两种排序也还行,但是还是希望可以根据类中方法顺序进行排序。
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.docExpansion(DocExpansion.NONE) // 接口文档展开
.operationsSorter(OperationsSorter.ALPHA) // http操作排序
.defaultModelRendering(ModelRendering.EXAMPLE) // Model渲染
.tagsSorter(TagsSorter.ALPHA)
.build();
}
自 V2.9 后,参数顺序就变为字母顺序,不能更改,具体 ISSUE: 2.9.0 change the order of parameters in operations。这非常影响查看,比如参数 startTime 和 endTime 就分隔开来了,希望改成类中的字段顺序。
@ApiModelProperty position 失效,具体 ISSUE : Model properties are alphabetically sorted (without order attribute),维护者说会在v3.0.1 版本修复这个 bug ,但是已经两年没更新,最新版本是 v3.0.0.