什么是 Swagger
Swagger 是一系列 RESTful API 的工具,通过 Swagger 可以获得项目的一种交互式文档,客户端 SDK 的自动生成等功能。
Swagger 的目标是为 REST APIs 定义一个标准的、与语言无关的接口,使人和计算机在看不到源码或者看不到文档或者不能通过网络流量检测的情况下,能发现和理解各种服务的功能。当服务通过 Swagger 定义,消费者就能与远程的服务互动通过少量的实现逻辑。类似于低级编程接口,Swagger 去掉了调用服务时的很多猜测。
Swagger(丝袜哥)是世界上最流行的 API 表达工具。
Swagger 是一个简单但功能强大的 API 表达工具。它具有地球上最大的 API 工具生态系统,数以千计的开发人员,使用几乎所有的现代编程语言,都在支持和使用 Swagger。使用 Swagger 生成 API,我们可以得到交互式文档,自动生成代码的 SDK 以及 API 的发现特性等。
使用 Spring Boot 集成 Swagger 的理念是,使用注解来标记出需要在 API 文档中展示的信息,Swagger 会根据项目中标记的注解来生成对应的 API 文档。Swagger 被号称世界上最流行的 API 工具,它提供了 API 管理的全套解决方案,API 文档管理需要考虑的因素基本都包含,这里将讲解最常用的定制内容。
快速上手
Spring Boot 集成 Swagger 2.X 很简单,需要引入依赖并做基础配置即可,下面我们来感受一下。
添加依赖包
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
创建 SwaggerConfig 配置类
package com.example.swagger.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
//配置类,开启Swagger2
@Configuration
@EnableSwagger2
public class SwaggerConfig{
private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("客户管理")
.description("客户管理中心 API 1.0接口文档")
.termsOfServiceUrl("http://www.baidu.com/")
.version("1.0.0")
.contact(new Contact("Jacky","http://www.baidu.com","hhhhbk@126.com"))
.build();
}
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//指定使用Swagger接口的包路径
.apis(RequestHandlerSelectors.basePackage("com.example.swagger.controller"))
.paths(PathSelectors.any())
.build();
}
}
@Configuration,启动时加载此类
@EnableSwagger2,表示此项目启用 Swagger API 文档
此方法使用 @Bean,在启动时初始化,返回实例 Docket(Swagger API 摘要),这里需要注意的是 .apis(RequestHandlerSelectors.basePackage("com.neo.xxx")) 指定需要扫描的包路径,只有此路径下的 Controller 类才会自动生成 Swagger API 文档。
这块配置相对重要一些,主要配置页面展示的基本信息包括,标题、描述、版本、服务条款、联系方式等,查看 ApiInfo 类的源码还会发现支持 license 配置等。
public class ApiInfo {
public static final Contact DEFAULT_CONTACT = new Contact("", "", "");
public static final ApiInfo DEFAULT;
private final String version;
private final String title;
private final String description;
private final String termsOfServiceUrl;
private final String license;
private final String licenseUrl;
private final Contact contact;
private final List<VendorExtension> vendorExtensions;
//...
}
以上信息皆可在此方法进行配置,也可以使用默认值。配置完成之后启动项目,在浏览器中输入网址 http://localhost:8080/swagger-ui.html,即可看到上面的配置信息,效果如下:
访问地址后,发现页面存在这样一句话:No operations defined in spec!,意思是没有找到相关的 API 内容,这是因为还没有添加对应的 Controller 信息,接下来结合代码一一介绍各个注解的使用。
创建Model层
package com.example.swagger.model;
public class User {
private long id;
private String name;
private String age;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
package com.example.swagger.model;
import java.util.Date;
public class Message {
private long id;
private String text;
private String summary;
private Date createDate;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", text='" + text + '\'' +
", summary='" + summary + '\'' +
", createDate=" + createDate +
'}';
}
}
创建Repository层
package com.example.swagger.repository;
import com.example.swagger.model.Message;
import java.util.List;
//消息操作接口
public interface MessageRepository {
List<Message> findAll();
Message save(Message message);
Message update(Message message);
Message updateText(Message message);
Message findMessage(Long id);
void deleteMessage(Long id);
}
package com.example.swagger.repository;
import com.example.swagger.model.Message;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
@Service("messageRepository")
public class InMemoryMessageRepository implements MessageRepository {
// 创建long原子操作对象
private static AtomicLong counter = new AtomicLong();
// 创建当前一个Map泛型对象(模拟数据库存储Message类数据.....)
private final ConcurrentMap<Long,Message> messageMap = new ConcurrentHashMap<>();
@Override
public List<Message> findAll() {
List<Message> messagesList = new ArrayList<Message>(this.messageMap.values());
return messagesList;
}
@Override
public Message save(Message message) {
Long id = message.getId();
if(id == null){
// 获取自动增长的long id
id = counter.incrementAndGet();
message.setId(id);
}
this.messageMap.put(id,message);
return message;
}
@Override
public Message update(Message message) {
this.messageMap.put(message.getId(),message);
return message;
}
@Override
public Message updateText(Message message) {
Message msg = this.messageMap.get(message.getId());
msg.setText(message.getText());
this.messageMap.put(msg.getId(),msg);
return msg;
}
@Override
public Message findMessage(Long id) {
return this.messageMap.get(id);
}
@Override
public void deleteMessage(Long id) {
this.messageMap.remove(id);
}
}
创建统一数据返回接口
package com.example.swagger.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(description = "响应对象")
public class BaseResult<T> {
private static final int SUCCESS_CODE = 200;
private static final String SUCCESS_MESSAGE = "成功";
@ApiModelProperty(name = "code", value = "响应编码", required = true, example = "" + SUCCESS_CODE)
private int code;
@ApiModelProperty(name = "msg", value = "响应消息", required = true,example = SUCCESS_MESSAGE)
private String msg;
@ApiModelProperty(name = "data", value = "响应数据")
private T data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
// 私有构造器
private BaseResult(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
private BaseResult(int code, String msg) {
this(code,msg,null);
}
private BaseResult(T data) {
this(SUCCESS_CODE,SUCCESS_MESSAGE,data);
}
private BaseResult() {
this(SUCCESS_CODE,SUCCESS_MESSAGE);
}
// 创建返回对象公开静态接口
public static <T> BaseResult<T> success(){
return new BaseResult<>();
}
public static <T> BaseResult<T> successWithData(T data){
return new BaseResult<>(data);
}
public static <T> BaseResult<T> failWitchCodeAndMsg(int code, String msg){
return new BaseResult<>(code,msg,null);
}
public static <T> BaseResult<T> buildWithParam(ResponseParam param){
return new BaseResult<>(param.code,param.msg,null);
}
//静态内部类
public static class ResponseParam{
private int code;
private String msg;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
// 私有构造器
private ResponseParam(int code, String msg) {
this.code = code;
this.msg = msg;
}
// 公开创建ResponseParam静态对象的方法
public static ResponseParam buildParam(int code, String msg){
return new ResponseParam(code,msg);
}
} //静态内部类结束
}
创建相关Controller(包含Swagger注解)和Test
package com.example.swagger.controller;
import com.example.swagger.config.BaseResult;
import com.example.swagger.model.User;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@Api(value = "用户管理", description = "用户管理API",position = 100,protocols = "http")
@RestController
@RequestMapping(value = "/user")
public class UserController {
//创建Map对象
static Map<Long,User> userMap = Collections.synchronizedMap(new HashMap<>());
@ApiOperation(value = "获取用户列表",notes = "查询用户列表")
@ApiResponses({
@ApiResponse(code = 100, message = "异常数据")
})
@RequestMapping(value = {""},method = RequestMethod.GET)
public List<User> getUserList(){
return new ArrayList<>(userMap.values());
}
@ApiOperation(value = "获取用户详细信息", notes = "根据url传的id来获取用户详细信息")
@ApiImplicitParam(name = "id",value = "用户ID", required = true, dataType = "Long", paramType = "path")
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUser(@PathVariable Long id){
return userMap.get(id);
}
@ApiOperation(value = "创建用户",notes = "根据User对象创建用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "name", value = "用户名", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "age", value = "年龄", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "ipAddr", value = "ip地址", required = true, dataType = "String", paramType = "query")
})
@RequestMapping(value = "", method = RequestMethod.POST)
public BaseResult<User> postUser(User user){
userMap.put(user.getId(),user);
return BaseResult.successWithData(user);
}
@ApiOperation(value = "更新用户信息",notes = "根据用户ID更新用户信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "name", value = "用户名", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "age", value = "年龄", required = true, dataType = "String", paramType = "query")
})
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public BaseResult<User> putUser(@PathVariable Long id, User user){
User myUser = userMap.get(id);
myUser.setName(user.getName());
myUser.setAge(user.getAge());
userMap.put(id,myUser);
return BaseResult.successWithData(user);
}
@ApiOperation(value = "删除用户信息", notes = "根据url传的id来删除用户信息")
@ApiImplicitParam(name = "id",value = "用户ID", required = true, dataType = "Long", paramType = "path")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id){
userMap.remove(id);
return "success";
}
}
package com.example.swagger.controller;
import com.example.swagger.config.BaseResult;
import com.example.swagger.model.Message;
import com.example.swagger.repository.MessageRepository;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(value = "消息", description = "消息操作 API",position = 100, protocols = "http")
@RestController
@RequestMapping("/")
public class MessageController {
@Autowired
private MessageRepository messageRepository;
@ApiOperation(value = "消息列表", notes = "完整的消息内容列表", produces = "application/json",
consumes = "application/json", response = List.class)
@GetMapping(value = "messages")
public List<Message> list(){
List<Message> messageList = this.messageRepository.findAll();
return messageList;
}
@ApiOperation(value = "创建消息", notes = "根据消息对象创建消息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "消息 ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "text", value = "消息正文", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "summary", value = "消息摘要", required = true, dataType = "String", paramType = "query")
})
@PostMapping(value = "message")
public Message create(Message message){
System.out.println("message======" + message.toString());
message = this.messageRepository.save(message);
return message;
}
@ApiOperation(value = "修改消息", notes = "根据消息对象修改消息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "消息 ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "text", value = "消息正文", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "summary", value = "消息摘要", required = true, dataType = "String", paramType = "query")
})
@ApiResponses({
@ApiResponse(code = 100, message = "请求参数错误"),
@ApiResponse(code = 300, message = "未授权"),
@ApiResponse(code = 403, message = "禁止访问"),
@ApiResponse(code = 404, message = "请求路径不存在"),
@ApiResponse(code = 500, message = "服务器内部错误")
})
@PutMapping(value = "message")
public Message modify(Message message){
Message messageResult = this.messageRepository.update(message);
return messageResult;
}
@PatchMapping(value = "/message/text")
public BaseResult<Message> patch(Message message){
Message messageResult = this.messageRepository.updateText(message);
return BaseResult.successWithData(messageResult);
}
@GetMapping(value = "message/{id}")
public Message get(@PathVariable Long id){
Message message = this.messageRepository.findMessage(id);
return message;
}
@DeleteMapping(value = "message/{id}")
public void delete(@PathVariable("id") Long id){
this.messageRepository.deleteMessage(id);
}
}
package com.example.swagger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SwaggerApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
saveMessages();
}
private void saveMessages() {
for(int i=1;i<10;i++){
final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.add("id", "" + i);
params.add("text", "text" + i);
params.add("summary", "summary" + i);
try {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/message")
.params(params)).andReturn();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
public void saveMessage()throws Exception{
final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.add("text","text");
params.add("summary","summary");
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/message")
.params(params)).andReturn().getResponse().getContentAsString();
System.out.println("saveMessage Result ====== " + mvcResult);
}
@Test
public void getAllMessage()throws Exception{
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/messages"))
.andReturn().getResponse().getContentAsString();
System.out.println("getAllMessage Result ====== " + mvcResult);
}
@Test
public void getMessage()throws Exception{
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/message/6"))
.andReturn().getResponse().getContentAsString();
System.out.println("getMessage Result ====== " + mvcResult);
}
@Test
public void modifyMessage()throws Exception{
final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.add("id","6");
params.add("text","text");
params.add("summary","summary");
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/message").params(params))
.andReturn().getResponse().getContentAsString();
System.out.println("modifyMessage Result ====== " + mvcResult);
}
@Test
public void patchMessage()throws Exception{
final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.add("id","6");
params.add("text","text");
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.patch("/message/text").params(params))
.andReturn().getResponse().getContentAsString();
System.out.println("patchMessage Result ====== " + mvcResult);
}
@Test
public void deleteMessage()throws Exception{
mockMvc.perform(MockMvcRequestBuilders.delete("/message/6")).andReturn();
String mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/messages"))
.andReturn().getResponse().getContentAsString();
System.out.println("patchMessage Result ====== " +mvcResult);
}
}
S直接在浏览器中输入:http://localhost:8080/swagger-ui.html,如下:
Swagger 常用注解
Swagger 通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息等,常用注解内容如下:
@Api 的使用
Api 作用在 Controller 类上,做为 Swagger 文档资源,该注解将一个 Controller(Class)标注为一个 Swagger 资源(API)。在默认情况下,Swagger-Core 只会扫描解析具有 @Api 注解的类,而会自动忽略其他类别资源(JAX-RS endpoints、Servlets 等)的注解。
使用示例:
@Api(value = "消息", description = "消息操作 API",position = 100, protocols = "http")
@RestController
@RequestMapping("/")
public class MessageController {}
与 Controller 注解并列使用,属性配置如表所示:
自动将 MessageController 内的方法都添加了映射,并标明了每种方法的请求方式。
@ApiOperation 的使用
ApiOperation 定义在方法上,描述方法名、方法解释、返回信息、标记等信息。
使用示例:
@ApiOperation(value = "消息列表", notes = "完整的消息内容列表", produces = "application/json",
consumes = "application/json", response = List.class)
@GetMapping(value = "messages")
public List<Message> list(){
List<Message> messageList = this.messageRepository.findAll();
return messageList;
}
@ApiImplicitParams 和 @ApiImplicitParam 的使用
@ApiImplicitParams 用于描述方法的返回信息,和 @ApiImplicitParam 注解配合使用;@ApiImplicitParam 用来描述具体某一个参数的信息,包括参数的名称、类型、限制等信息。
使用示例:
@ApiOperation(value = "创建消息", notes = "根据消息对象创建消息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "消息 ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "text", value = "消息正文", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "summary", value = "消息摘要", required = true, dataType = "String", paramType = "query")
})
@PostMapping(value = "message")
public Message create(Message message){
System.out.println("message======" + message.toString());
message = this.messageRepository.save(message);
return message;
}
@ApiResponses 和 @ApiResponse 的使用
@ApiResponses 主要封装方法的返回信息和 @ApiResponse 配置起来使用,@ApiResponse 定义返回的具体信息包括返回码、返回信息等。
使用示例:
@ApiOperation(value = "修改消息", notes = "根据消息对象修改消息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "消息 ID", required = true, dataType = "Long", paramType = "query"),
@ApiImplicitParam(name = "text", value = "消息正文", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "summary", value = "消息摘要", required = true, dataType = "String", paramType = "query")
})
@ApiResponses({
@ApiResponse(code = 100, message = "请求参数错误"),
@ApiResponse(code = 300, message = "未授权"),
@ApiResponse(code = 403, message = "禁止访问"),
@ApiResponse(code = 404, message = "请求路径不存在"),
@ApiResponse(code = 500, message = "服务器内部错误")
})
@PutMapping(value = "message")
public Message modify(Message message){
Message messageResult = this.messageRepository.update(message);
return messageResult;
}
@ApiModel 和 @ApiModelProperty 的使用
在实际的项目中我们常常会封装一个对象作为返回值,@ApiModel 就是负责描述对象的信息,@ApiModelProperty 负责描述对象中属性的相关内容。
使用示例:
@ApiModel(description = "响应对象")
public class BaseResult<T> {
private static final int SUCCESS_CODE = 200;
private static final String SUCCESS_MESSAGE = "成功";
@ApiModelProperty(name = "code", value = "响应编码", required = true, example = "" + SUCCESS_CODE)
private int code;
@ApiModelProperty(name = "msg", value = "响应消息", required = true,example = SUCCESS_MESSAGE)
private String msg;
@ApiModelProperty(name = "data", value = "响应数据")
private T data;
}
属性配置如下表所示:
这样我们在 Controller 中封装返还信息时就可以这样操作:
@PatchMapping(value = "/message/text")
public BaseResult<Message> patch(Message message){
Message messageResult = this.messageRepository.updateText(message);
return BaseResult.successWithData(messageResult);
}
以上就是在项目中最常用的一些注解,灵活地利用这些注解就可以自动构建出清晰的 API 文档。
Try it out
使用 Swagger 创建的在线 API 还有一个非常强大的功能,可以在页面直接测试接口的可用性,这样在前端和后端接口调试出现问题时,可以非常方便地利用此功能进行接口验证。在上面参数讲解过程中,我们发现每个接口描述右侧都有一个按钮 try it out,单击 try it out 按钮即可进入表单页面,如下:
在表单页面添加相关字段后,单击“Execute”按钮就会将请求发送到后台,从而进行接口验证,通过按钮下面的命令可以看出,实际上是使用了 curl 命令进行的 post 测试:
在后端调整 Swagger 方法上对应参数,即可看到 curl 命令参数的变化。
总结
利用 Swagger 的相关注解可以容易地构建出丰富的 API 文档。使用 Swagger 之后可以帮助生成标准的 API 说明文档,避免接口交互中的低效沟通问题,Swagger 做为强大的 API 生成框架其实还有更多的功能,抛砖引玉学,可多多深究。