Spring boot 集成swagger-ui 在线API文档生成自我实践记录
目录
- @ApiModelProperty() 用于model类的属性
一、swagger简介
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的API文档生成工具。
Swagger™的目标是为REST APIs 定义一个标准的,与语言无关的接口,使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 让部署管理和使用功能强大的API从未如此简单。
二、swagger原理
首先看下springfox对文档Documentation的定义:
文档Documentation定义得很清晰,主要由groupName(分组名)、basePath(contextPath)、apiListings(API列表集)、resourceListing(资源列表集)等属性组成。
其中API列表被封装成ApiListing。ApiListing中又持有ApiDesciption集合引用,每个ApiDesciption都持有一个API集合的引用,Operation也就是具体的接口操作,内部包含了该接口对应的http方法、produces、consumes、协议、参数集、响应消息集等诸多元素。
springfox通过spring-plugin的方式将Plugin注册到Spring上下文中,然后使用这些plugin进行API的扫描工作,这里的扫描工作其实也就是构造Documentation的工作,把扫描出的结果封装成Documentation并放入到DocumentationCache内存缓存中,之后swagger-ui界面展示的API信息通过Swagger2Controller暴露,Swagger2Controller内部直接从DocumentationCache中寻找Documentation。
下面取部分plugin来看一下构造对应文档信息的过程:
从代码细节中入手,我们可以看到,入口处在@EnableSwagger2这个注解上,这个注解会导入一个配置类的Swagger2DocumentationConfiguration。
这个Swagger2DocumentationConfiguration做的事情如下:
1. 构造Bean。比如:HandlerMapping(HandlerMapping是springmvc中用于处理请求与handler(controller中的方法)之间映射关系的接口,springboot中默认使用的HandlerMapping是RequestMappingHandlerMapping),Swagger2DocumentationConfiguration配置类里构造的是PropertySourcedRequestMappingHandlerMapping,该类继承RequestMappingHandlerMapping。
2. import其它配置类,比如SpringfoxWebMvcConfiguration、SwaggerCommonConfiguration(SpringfoxWebMvcConfiguration配置类做的事情跟Swagger2DocumentationConfiguration类似)
3. 扫描指定包下的类,并注册到Spring上下文中
具体的API解析、扫描过程,这里不再展开细致的说明,可参考博文:https://yq.aliyun.com/articles/599809?utm_content=m_1000002417
总体上处理过程整理为下图:
三、为什么选择swagger?
手写Api文档的几个痛点
- 文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时;
- 接口返回结果不明确;
- 不能直接在线测试接口,通常需要使用工具,比如postman;
- 接口文档太多,不好管理;
swagger的优势
- 使用Swagger UI生成的界面比Javadoc生成的界面美观
- swagger可以实时同步API文档(代码修改后,文档同步修改)
- swagger解析速度快,效率高(使用轻量级数据交换格式JSON)
- 对现有SpringMVC工程支持友好
- Swagger可以充当前后端交流的重要桥梁,方便快捷。很实用。
- Swagger项目允许你生产,显示和消费你自己的RESTful服务。不需要代理和第三方服务。
四、如何集成swagger到我们的项目?
说明:这里是在Spring boot的基础上集成swagger
- 添加maven依赖
<!-- 引入swagger依赖 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency>
- 定义配置类
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @Descrption swagger2配置类 * @Date 2019-03-05 11:03 * @Version 1.0 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.xxx.xxx.api.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SzcApi项目在线API") .description("接口风格统一为Restful") .build(); } protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/METE-INF/resources/"); registry.addResourceHandler("webjars/**") .addResourceLocations("classpath:/METE-INF/resources/webjars/"); } }
如上代码所示,
@Configuration注解,作用是让Spring来加载该类配置,@EnableSwagger2注解来启用Swagger2。
createRestApi函数创建Docket的Bean之后,通过apiInfo()来创建该Api的基本信息,这些基本信息会展现在文档页面中。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,我们这里采用指定扫描的包路径来定义,Swagger会扫描这个包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。
到这里,我们就已经做到把swagger集成到项目中了,启动服务之后访问url:http://localhost:7700/swagger-ui.html
,就可以看到swagger的API文档了。
注意:如果项目中有权限拦截,比如URL白名单,需要将swagger的路径加入到白名单列表中:
"/swagger", "/webjars", "/v2/api"
五、总结常用swagger-ui注解
1)API的注解
对于API的设计,一般倾向于将功能相同的API归集为一组。在Spring Boot中,利用Controller来实现,每个Controller里包含若干个REST API,而每个API都有输入及输出值。所以swagger对API的注解也是参照这个层级来划分与实现的。其逻辑结果如下图:
- @Api() 用于类
该注解将一个Controller(Class)标注为一个swagger资源(API)。在默认情况下,Swagger-Core只会扫描解析具有@Api注解的类,而会自动忽略其他类别资源(JAX-RS endpoints,Servlets等等)的注解。该注解包含以下几个重要属性:
- tags:API分组标签。具有相同标签的API将会被归并在一组内展示。
- value:如果tags没有定义,value将作为Api的tags使用
- description:对该API的详细描述信息
- position:如果一个controller中有多个请求方法,可以通过该属性来指定API在swagger-ui中的显示顺序
- @ApiOperation() 用于方法
在指定的(路由)路径上,对一个操作或HTTP方法进行描述。具有相同路径的不同操作会被归组为同一个操作对象。不同的HTTP请求方法及路径组合构成一个唯一操作。此注解的属性有:
- value:对操作的简单说明,长度为120个字母,60个汉字。
- notes:对操作的详细说明。
- httpMethod:HTTP请求的动作名,可选值有:"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" and "PATCH"。
- code:默认为200,有效值必须符合标准的HTTP Status Code Definitions。
- @ApiParam() 用于方法,参数,字段说明
增加对参数的元信息说明,主要的属性有:
- required:指定该参数是否为必传参数
- value:对该参数含义的简短说明
- @ApiResponses()用于包装类
注解@ApiResponse的包装类,数组结构。
即使需要使用一个@ApiResponse注解,也需要将@ApiResponse注解包含在注解@ApiResponses内。
- @ApiResponse()用于方法的返回结果
描述一个操作可能的返回结果。
当REST API请求发生时,这个注解可用于描述所有可能的成功与错误码。
可以用,也可以不用这个注解去描述操作的返回类型,但成功操作的返回类型必须在@ApiOperation中定义。
如果API具有不同的返回类型,那么需要分别定义返回值,并将返回类型进行关联。
但Swagger不支持同一返回码,多种返回类型的注解。注意:这个注解必须被包含在@ApiResponses注解中。
字段说明:
- code:HTTP请求返回码。有效值必须符合标准的HTTP Status Code Definitions。
- message:用于对返回信息作详细说明,对请求结果的描述信息
- response:返回类型信息,必须使用完全限定类名,比如“com.xyz.cc.Person.class”。
- responseContainer:如果返回类型为容器类型,可以设置相应的值。有效值为 "List", "Set" or "Map",其他任何无效的值都会被忽略
2)Model的注解
- @ApiModel() 用于类
提供对Swagger model额外信息的描述。在标注@ApiOperation注解的操作内,所有的类将自动被内省(introspected),但利用这个注解可以做一些更加详细的model结构说明。主要属性有:
- value:model的别名,默认为类名
- description:对model的详细描述
- @ApiModelProperty() 用于model类的属性
表示对model属性的说明或者数据操作更改,主要的属性有:
- value:给出该属性的简短描述
- required:标识该属性是否为必须值
- example:给出该属性的示例值
3)其他注解
- @ApiIgnore() 用于类,方法,方法参数
表示这个方法或者类被忽略
- @ApiImplicitParam() 用于方法
表示单独的请求参数
- @ApiImplicitParams() 用于方法
该注解可以包含多个 @ApiImplicitParam
六、接口分组
接口分组就是将多个接口按照功能,或者按照模块分成多个组,这样查看AP文档的时候,可以相对清晰一些。
当然,分组只是推荐的处理方式,你也可以不分组。下面我们看一下分组有哪些实现方式:
路径分组
通过RequestHandlerSelectors.basePackage(“”)指定扫描的包路径。
假设我们在做一个学校的管理系统,后台用户分为校长、老师、学生。我们可以按照如下的方式对controller进行细分。
在我们的swagger配置类中增加三个Docket,配置如下:
@Bean
public Docket createPresidentDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("校长").apiInfo(presidentApiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("pri.fly.leaning.controller.president"))
.paths(PathSelectors.any()).build();
}
@Bean
public Docket createStudentDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("学生").apiInfo(studentApiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("pri.fly.leaning.controller.student"))
.paths(PathSelectors.any()).build();
}
@Bean
public Docket createTeacherDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("老师").apiInfo(teacherApiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("pri.fly.leaning.controller.teacher"))
.paths(PathSelectors.any()).build();
}
然后我们就看到swagger UI的space处就可以选择分组了:
注解分组
上文提到的路径分组是一种分组方式,但是这种分组的局限比较大,万一我们的代码中没有按照这种方式分包那就无法分组。现在来介绍一个更灵活的方式:注解分组
仍然以上面提到的学校管理系统为例,校长和老师都属于学校管理层,我们希望将他们放到同一个分组中。首先定义我们自己的分组注解。
/**
* 学校管理层分组注解
* @author fly
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Manager {
}
在对应的controller上加上我们自己定义的注解:
@RestController
@RequestMapping("/teacher/users")
@Api(description = "老师班级管理接口")
@Manager
public class ClassController {
....
}
@RestController
@RequestMapping("/president/school")
@Api(description = "校长学校管理接口")
@Manager
public class SchoolController {
....
}
配置Docket
核心:RequestHandlerSelectors.withClassAnnotation(Manager.class)
@Bean
public Docket createManagerDocket() {
return new Docket(DocumentationType.SWAGGER_2).groupName("管理者").apiInfo(managerApiInfo()).select()
.apis(RequestHandlerSelectors.withClassAnnotation(Manager.class))
.paths(PathSelectors.any()).build();