由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。
这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题:
为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。
在pom.xml
中加入Swagger2的依赖坐标
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.2.2version>
dependency>
注意这个配置类需要放到Application.java
所在的同一目录或者子目录下。
@SpringBootConfiguration
public class Swagger2Config {
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.itcrud.swagger"))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(parameterBuilder());
}
//API的基本信息
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("IT-CRUD接口文档")
.description("专注于学习笔记,想和我一起学习,请关注博客:http://blog.itcrud.com")
.termsOfServiceUrl("http://blog.itcrud.com")
.contact("IT-CRUD")
.version("1.0.0")
.build();
}
//请求参数构建
private List<Parameter> parameterBuilder() {
List<Parameter> parameters = Lists.newArrayList();
parameters.add(new ParameterBuilder().name("token").description("登录验证")
.modelRef(new ModelRef("string")).required(false)
.parameterType("header").build());
return parameters;
}
}
通过createRestApi
函数创建Docket
的Bean之后,apiInfo()
用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()
函数返回一个ApiSelectorBuilder
实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore
指定的请求)。另外一般项目可能都会用到请求头信息,这里通过ParameterBuilder
构建头信息部分,添加到全局选项参数globalOperationParameters
中。
在完成了上述配置后,其实已经可以生成文档内容,但是这样的文档主要针对请求本身,而描述主要来源于函数等命名产生,对用户并不友好,我们通常需要自己增加一些说明来丰富文档内容。需要介绍几个常用的注解。
@Api
:放在Controller类上面,对类做整体的介绍,主要使用description
、value
属性@ApiOperation
:放在接口方法上,用来描述方法的信息,常用属性是value
和notes
@ApiModel
:放在接收请求参数类或者响应参数类上,用来描述类信息,常用属性是value
@ApiModelProperty
:方法接收请求参数类或者响应参数类的属性字段上,用来描述字段信息,常用属性是value
@ApiImplicitParam
:放在接口方法上,用来描述参数信息,常用属性有name
、value
、required
、dataType
、paramType
。(此注解不推荐用)@ApiImplicitParams
:放在接口方法上,用来描述参数信息,描述多个字段信息,其内部属性就一个,是@ApiImplicitParam
数组,所以常用属性也是name
、value
、required
、dataType
、paramType
。(此注解不推荐用)这是个Controller类,里面包含了上面的所有注解。
@Api(description = "UserController", value = "用户服务接口")
@Controller
@RequestMapping("/user")
public class UserController {
@ApiOperation("添加用户信息")
@PostMapping("/addUser")
@ResponseBody
public String addUser(@RequestBody UserReqDTO reqDTO) {
return "";
}
@ApiOperation("删除用户信息")
@DeleteMapping("/deleteUser")
@ResponseBody
public String deleteUser(@RequestParam(name = "userId") Long userId) {
return "";
}
@ApiOperation("编辑用户信息")
@PutMapping("/editUser")
@ResponseBody
public String editUser(@RequestBody UserReqDTO reqDTO) {
return "";
}
@ApiOperation(value = "查询用户信息", notes = "查询用户信息")
@GetMapping("/getUser")
@ResponseBody
public List<User> getUser(@ModelAttribute UserSearchReqDTO reqDTO) {
return Lists.newArrayList();
}
@ApiOperation(value = "测试header信息", notes = "测试swagger配置中添加的header参数token在传入的时候是否在头信息里面")
@GetMapping("/testHeader")
@ResponseBody
public String testHeader(HttpServletRequest request) {
System.out.println("header-->" + request.getHeader("token"));
return "";
}
@ApiOperation(value = "模糊查询用户", notes = "根据手机号、用户名模糊查询用户信息")
@GetMapping("/getUserByKeywords")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName", value = "用户名", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "userPhone", value = "手机号码", required = true, dataType = "String", paramType = "query")
})
@ResponseBody
public List<User> getUsers(@RequestParam("userName") String userName, @RequestParam("userPhone") String userPhone) {
return Lists.newArrayList();
}
}
实体类包含请求的参数类和响应的参数类。
@Data
@ApiModel(value = "用户详细信息")
public class User {
@ApiModelProperty(value = "用户ID")
private Long id;
@ApiModelProperty(value = "用户姓名")
private String userName;
@ApiModelProperty(value = "用户手机号码")
private String userPhone;
@ApiModelProperty(value = "用户性别")
private Integer gender;
@ApiModelProperty(value = "用户年龄")
private Integer age;
@ApiModelProperty(value = "注册时间")
private Date registerTime;
}
//-----------------------
@Data
@ApiModel(value = "用户参数类")
public class UserReqDTO {
@ApiModelProperty(value = "用户ID")
private Long id;
@ApiModelProperty(value = "用户姓名")
private String userName;
@ApiModelProperty(value = "用户手机号码")
private String userPhone;
@ApiModelProperty(value = "用户性别")
private Integer gender;
@ApiModelProperty(value = "用户年龄")
private Integer age;
}
//-----------------------
@Data
@ApiModel(value = "用户查询类")
public class UserSearchReqDTO {
@ApiModelProperty(value = "用户编号")
private Long id;
@ApiModelProperty(value = "查询关键字(用户名+手机号模糊查询)")
private String keywords;
}
完成上面的代码就可以启动这个springboot项目了。然后直接访问:http://localhost:8080/swagger-ui.html。这个地址根据实际项目不同,但是都是swagger-ui.html
结尾。
得到的结果如下:
上面的图是整体页面的数据,再展示一下具体接口内部的细节图。
上面细节图中的描述信息、字段名称、说明文字等对应到注解上的属性可以对照看一下,不多解释啦。另外要解释一下上面说@ApiImplicitParams
和@ApiImplicitParam
不推荐用的原因。
@ApiImplicitParams
、@ApiImplicitParam
原因@ApiImplicitParam
和参数对象类上的@ApiModel
系列的注解有兼容性问题,会打乱swagger上的显示,如下两张对比图:图一
图二
对比图一和图二,图一是不使用@ApiImplicitParam
的效果,图二是和@ApiModel
混用的效果。很明显的看出来,不使用@ApiImplicitParam
更优。这里的请求类UserReqDTO
类的参数不多,如果多的话有没有自动填充的效果,会是一件很痛苦的事。
@ApiImplicitParam
注解来标识参数的时候,需要注明此参数的类型,也就是paramType
必填,如果不填很可能导致请求参数不能传入,出现问题。看下面的两张对比图:图三
图四
这两个图是请求同一个接口(GET请求),图三请求的时候,该接口的@ApiImplicitParam
注解将paramType
值设置为了query
,图四是将这个属性去掉,默认的paramType
就是body
。很容易就会出错。那么应该怎么设置这个类型呢?有哪些类型?下面看一下:
path
:参数是链接上的占位符,如:http://blog.itcrud.com/api/getUserById/{id}
,id参数对应的类型就是path
query
:get请求,拼在地址后面的。如http://blog.itcrud.com/api/getUserById?id=123
,id参数对应的类型就是query
body
:post请求对应的body体,这个就不多说啦(json体写着麻烦,自己意会)header
:请求头数据,在请求的时候,常用@RequestHeader
来接收的参数就是请求头内的数据form
:form表单,不解释从上面的两个例子,加上对@ApiImplicitParam
的巨坑参数paramType
的理解,就可以知道为什么这个注解不推荐使用啦。但是在请求参数比较多,而且零散的时候可以考虑使用一下,但是需要注意避坑即可。
码云(gitee):https://gitee.com/itcrud/itcrud-note/tree/master/itcrud-note-1-1