Spring Boot 技术探索

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

21、Spring Boot之使用Swagger2构建RESTful APIs文档

平台环境:

名称

版本号

Mac OS X

10.14.6

JDK

1.8.0_201

Apache Maven

3.6.0

IntelliJ IDEA

2019.1 (Ultimate Edition)

Spring Boot

2.1.8.RELEASE

 

  什么是Swagger?官网宣称它是最佳的APIs构建工具集。举个例子,实际开发中前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。随着时间推移,版本迭代,接口文档与实际情况的差异会越来越大。

  此时就需要Swagger出场了,Swagger指出你只需要按照它的规范去定义接口及接口相关的信息,就可以自动生成接口文档和客户端服务端代码,做到调用端代码、服务端代码以及接口文档的一致性。然而,编写这个yml或json格式的描述文件,本身也是有一定负担,久而久之文档落后的问题又出现了。

  Spring不忍看到Swagger就这么沉沦下去,迅速将Swagger规范纳入自身的标准,建立了Spring-swagger项目,后面改成了现在的Springfox。改良后的Swagger通过扫描注解标签就可以自动生成描述文件,进而生成与代码一致的接口文档和客户端代码。这次真的可以说是喜大普奔啊。

 

Demo

pom.xml引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>


<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

 

Swagger需要先配置才能使用,新建配置类SwaggerConfig

package com.example.demo.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;


@Configuration
@EnableSwagger2
public class SwaggerConfig
{


    @Bean
    public Docket api()
    {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                // 需要生成文档的包路径
                .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller")).paths(PathSelectors.any()).build();
    }


    private ApiInfo apiInfo()
    {
        // 文档的标题、描述、服务条款网址、版本、联系方式等信息
        return new ApiInfoBuilder().title("微博客户端API").description("微博客户端API操作文档")
                .termsOfServiceUrl("http://codelabx.com/").version("1.0").contact(new Contact("代码研究室", "http://codelabx.com/", "admin@codelabx.com")).build();
    }
}

 

SwaggerConfig名称前有两个注解

  • @Configuration,表示当前类用做配置类,Spring Boot启动时加载此类。
  • @EnableSwagger2,表示此项目启用Swagger2。

RequestHandlerSelectors.basePackage("com.example.demo.controller")表示只有此路径下的Controller类才会自动生成Swagger API文档。

通过查看ApiInfoBuilder源码可以看到还有license等信息也可以配置。

/**
 * Builds the api information
 */
public class ApiInfoBuilder {
  private String title;
  private String description;
  private String termsOfServiceUrl;
  private Contact contact;
  private String license;
  private String licenseUrl;
  private String version;
  private List<VendorExtension> vendorExtensions = newArrayList();
// ...
}

 

现在就可以看看效果了,启动项目后浏览器访问http://127.0.0.1:8080/swagger-ui.html

提示No operations defined in spec!,是因为还没有编写controller类。

这次我们依旧以一套客户端app与服务器端通讯的API设计为例,学习Swagger2的使用。

在model包下新建Blog类

package com.example.demo.model;


public class Blog
{
    private Long id;
    private String title;
    private String content;


    public Long getId()
    {
        return id;
    }


    public void setId(Long id)
    {
        this.id = id;
    }


    public String getTitle()
    {
        return title;
    }


    public void setTitle(String title)
    {
        this.title = title;
    }


    public String getContent()
    {
        return content;
    }


    public void setContent(String content)
    {
        this.content = content;
    }


    public Blog(Long id, String title, String content)
    {
        this.setId(id);
        this.setTitle(title);
        this.setContent(content);
    }
}

 

新建DemoController

package com.example.demo.controller;


import com.example.demo.model.Blog;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;


import java.util.ArrayList;
import java.util.List;


@Api(tags = "微博API", protocols = "http")
@RestController
public class DemoController
{
    // 获取blog列表
    @GetMapping("Blogs")
    public List<Blog> Blogs()
    {
        System.out.println("select id, Title, Content from blogs");
        List<Blog> blogs = new ArrayList<>();
        blogs.add(new Blog(1L, "title1", "content1"));
        blogs.add(new Blog(2L, "title2", "content2"));
        return blogs;
    }


    // 发布一条blog
    @PostMapping("/Blog")
    public Blog create(Blog blog)
    {
        System.out.println("insert into blogs(Title, Content) values('" + blog.getTitle() + "','" + blog.getContent() + "')");
        return blog;
    }


    // 修改一条blog
    @PutMapping("/Blog")
    public Blog update(Blog blog)
    {
        System.out.println("update blogs set id=" + blog.getId() + ",Title='" + blog.getTitle() + "',Content='" + blog.getContent() + "' where id=" + blog.getId());
        return blog;
    }


    // 修改一条blog的正文
    @PatchMapping("/Blog/Content")
    public Blog patch(Blog blog)
    {
        System.out.println("update blogs set Content='" + blog.getContent() + "' where id=" + blog.getId());
        return blog;
    }


    // 获取一条blog
    @GetMapping("/Blog/{id}")
    public Blog get(@PathVariable Long id)
    {
        System.out.println("select id, Title, Content from blogs where id=" + id);
        Blog Blog = new Blog(id, "titleXXX", "contentXXX");
        return Blog;
    }


    // 删除一条blog
    @DeleteMapping("/Blog/{id}")
    public void delete(@PathVariable("id") Long id)
    {
        System.out.println("delete from blogs where id=" + id);
    }
}

 

启动项目,浏览器访问http://127.0.0.1:8080/swagger-ui.html

可以看到Swagger2扫描到了DemoController,并自动为我们生成了web版文档。

点击箭头展开折叠项目:

 

可以看到Swagger2生成的文档中,每个方法都是按照请求方式、请求路径、方法名的顺序作为标题。点击标题“POST/Blog create”展开后可以看到方法的请求参数以及返回信息。

 

这里可以看到一个问题,一个API接口对于的调用者来说不需要知道具体的服务端方法名(例如create)叫什么。反而换成说明文字能更好的促进对这个接口作用的理解。

此时就需要用到@ApiOperation标签了,我们在create方法前增加此标签。

// 发布一条blog
@ApiOperation(value = "发布一条blog", notes = "发布一条全新的博文")
@PostMapping("/Blog")
public Blog create(Blog blog)
{
    System.out.println("insert into blogs(Title, Content) values('" + blog.getTitle() + "','" + blog.getContent() + "')");
    return blog;
}

重启项目后看到:

 

还有一个问题,文档中的Parameters部分的Description一列是空的。这里就需要@ApiImplicitParams和@ApiImplicitParam标签了,我们在create方法前增加此标签。

// 发布一条blog
@ApiOperation(value = "发布一条blog", notes = "发布一条全新的博文")
@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "不需要指定ID", required = false, dataType = "String", paramType = "query"), @ApiImplicitParam(name = "title", value = "标题", required = true, dataType = "String", paramType = "query"), @ApiImplicitParam(name = "content", value = "内容", required = true, dataType = "String", paramType = "query"),})
@PostMapping("/Blog")
public Blog create(Blog blog)
{
    System.out.println("insert into blogs(Title, Content) values('" + blog.getTitle() + "','" + blog.getContent() + "')");
    return blog;
}

重启项目后看到:

 

接下来看Responses部分,首先是Response content type。这里默认的*/*是JSON格式显示,如果想改成XML格式呢?这就要用到@ApiOperation标签的produces和consumes属性。

修改Blogs()方法。

// 获取blog列表
@ApiOperation(value = "获取blog列表", notes = "获取全部已发布的blog列表", produces = "application/json, application/xml", consumes = "application/json, application/xml")
@GetMapping("Blogs")
public List<Blog> Blogs()
{
    System.out.println("select id, Title, Content from blogs");
    List<Blog> blogs = new ArrayList<>();
    blogs.add(new Blog(1L, "title1", "content1"));
    blogs.add(new Blog(2L, "title2", "content2"));
    return blogs;
}

重启项目后,展开标题“GET/Blogs获取blog列表”看到:

 

接下来看Responses部分的Code、Description部分。Swagger2默认给我们生成了几个常见的HTTP相应状态码2XX、4XX、5XX系列。如果想增加状态码则需要用到@ApiResponses、@ApiResponse标签。

修改update()方法。

// 修改一条blog
@ApiOperation(value = "修改一条blog", notes = "根据ID修改一条blog")
@ApiResponses({@ApiResponse(code = 100, message = "请求参数有误"), @ApiResponse(code = 101, message = "未授权"), @ApiResponse(code = 103, message = "禁止访问"), @ApiResponse(code = 104, message = "请求路径不存在"), @ApiResponse(code = 500, message = "服务器内部错误")})
@PutMapping("/Blog")
public Blog update(Blog blog)
{
    System.out.println("update blogs set id=" + blog.getId() + ",Title='" + blog.getTitle() + "',Content='" + blog.getContent() + "' where id=" + blog.getId());
    return blog;
}

重启项目,展开标题“PUT/Blog修改一条blog”看到:

 

接下来介绍@ApiModel、@ApiModelProperty标签的使用。

在上面的controller代码中,在一个方法需要返回一个对象时,直接就返回的对象本身。

例如:

// 修改一条blog的正文
@ApiOperation(value = "修改一条blog的正文", notes = "根据ID修改一条blog的正文")
@PatchMapping("/Blog/Content")
public Blog patch(Blog blog)
{
    System.out.println("update blogs set Content='" + blog.getContent() + "' where id=" + blog.getId());
    return blog;
}

然而实际开发中常常把返回的对象封装在一个专用返回对象中,这个对象叫响应对象。响应对象可以附带丰富的返回信息,例如:响应码、响应消息、响应数据。@ApiModel就是用来注解响应对象的,@ApiModelProperty用来注解响应对象的属性。

代码:

package com.example.demo.config;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * 通用响应对象
 */
@ApiModel(description = "响应对象")
public class BaseResult<T>
{
    private static final int SUCCESS_CODE = 0;
    private static final String SUCCESS_MESSAGE = "成功";

    @ApiModelProperty(value = "响应码", name = "code", required = true, example = "" + SUCCESS_CODE)
    private int code;

    @ApiModelProperty(value = "响应消息", name = "msg", required = true, example = SUCCESS_MESSAGE)
    private String msg;

    @ApiModelProperty(value = "响应数据", name = "data")
    private T data;

    private BaseResult(int code, String msg, T data)
    {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    private BaseResult()
    {
        this(SUCCESS_CODE, SUCCESS_MESSAGE);
    }

    private BaseResult(int code, String msg)
    {
        this(code, msg, null);
    }

    private BaseResult(T data)
    {
        this(SUCCESS_CODE, SUCCESS_MESSAGE, data);
    }

    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> failWithCodeAndMsg(int code, String msg)
    {
        return new BaseResult<>(code, msg, null);
    }

    public static <T> BaseResult<T> buildWithParam(ResponseParam param)
    {
        return new BaseResult<>(param.getCode(), param.getMsg(), null);
    }

    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;
    }
    
    public static class ResponseParam
    {
        private int code;
        private String msg;

        private ResponseParam(int code, String msg)
        {
            this.code = code;
            this.msg = msg;
        }

        public static ResponseParam buildParam(int code, String msg)
        {
            return new ResponseParam(code, 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;
        }
    }
}

 

修改Blog类的代码,也加上@ApiModelProperty注解。

package com.example.demo.model;


import io.swagger.annotations.ApiModelProperty;


public class Blog
{
    private Long id;
    @ApiModelProperty(value = "标题")
    private String title;
    @ApiModelProperty(value = "内容")
    private String content;


    public Long getId()
    {
        return id;
    }


    public void setId(Long id)
    {
        this.id = id;
    }


    public String getTitle()
    {
        return title;
    }


    public void setTitle(String title)
    {
        this.title = title;
    }


    public String getContent()
    {
        return content;
    }


    public void setContent(String content)
    {
        this.content = content;
    }


    public Blog(Long id, String title, String content)
    {
        this.setId(id);
        this.setTitle(title);
        this.setContent(content);
    }
}

 

此时我们就可以把controller中的返回值改成响应对象了。

// 修改一条blog的正文
@ApiOperation(value = "修改一条blog的正文", notes = "根据ID修改一条blog的正文")
@PatchMapping("/Blog/Content")
public BaseResult<Blog> patch(Blog blog)
{
    System.out.println("update blogs set Content='" + blog.getContent() + "' where id=" + blog.getId());
    return BaseResult.successWithData(blog);
}

 

重启项目,展开“PATCH/Blog/Content修改一条blog的正文”,可以看到:

 

Swagger2自动生成的web文档中还有一个模块叫Models,展开可以看到controller中所有的返回Model。

 

这里遇到一个BUG,当Model类的字段是大写字母开头的时候,标签不生效,造成自动生成的文档中缺少注释。

public class Blog
{
    private Long id;
    @ApiModelProperty(value = "标题")
    private String Title;
    @ApiModelProperty(value = "内容")
    private String Content;

    // 省略其他代码
}

 

改成小写的就没问题了。

public class Blog
{
    private Long id;
    @ApiModelProperty(value = "标题")
    private String title;
    @ApiModelProperty(value = "内容")
    private String content;

    // 省略其他代码
}

 

Swagger2创建的文档还有一个功能是Try it out,可以在页面上直接测试接口。

例如,展开标题“POST/Blog发布一条blog”,点击“Try it out”按钮。

填写参数之后点击“Execute”,可以看到Response body部分获取到了服务端返回的信息。

另外,通过Curl部分可以看到,实际上是使用curl命令进行的POST测试:

curl -X POST "http://127.0.0.1:8080/Blog?content=%E5%BE%AE%E5%8D%9A%E5%86%85%E5%AE%B9&title=%E5%BE%AE%E5%8D%9A%E6%A0%87%E9%A2%98" -H "accept: */*"

 

最后附上文档:

作用范围

API

使用位置

协议集描述

@Api

用于 Controller 类上

协议描述

@ApiOperation

用在 Controller 的方法上

非对象参数集

@ApiImplicitParams

用在 Controller 的方法上

非对象参数描述

@ApiImplicitParam

用在 @ApiImplicitParams 的方法里边

响应集

@ApiResponses

用在 Controller 的方法上

响应信息参数

@ApiResponse

用在 @ApiResponses 里边

描述返回对象的意义

@ApiModel

用在返回对象类上

对象属性

@ApiModelProperty

用在出入参数对象的字段上

 

@Api

将一个类标记为Swagger资源。

默认情况下,swagker-core只包含和纳入使用@Api注解的类,并且将忽略其他资源(JAX-RS endpoints、servlet等等)。

属性名称

备注

value

仅作为向下兼容保留,本质是tags标签。在swagger-core1.3中曾被用做API的路径。但在swagger-core 1.5.X中不再相关。如果tags标签没有使用,这个值将被用作操作资源的操作描述tag。如果tags存在,则此值将被移除。

tags

如果设置这个值,value 的值会被覆盖

description

对 API 资源的描述

produces

例如:"application/json, application/xml"

consumes

例如:"application/json, application/xml"

protocols

例如::http, https, ws, wss

authorizations

高级特性认证时配置

hidden

配置为 true 将在文档中隐藏

 

@ApiOperation

用来描述一个操作或者典型的特定路径的HTTP请求方法上。

一个同时包含着HTTP请求方法和一个路径的唯一操作。

属性名称

备注

value

对应操作方法的summary字段,提供此操作的简要说明。需小于120个字符以适配Swagger-UI的显示。

tags

如果设置这个值、value的 值会被覆盖

produces

For example, "application/json, application/xml"

consumes

For example, "application/json, application/xml"

protocols

Possible values: http, https, ws, wss

authorizations

高级特性认证时配置

hidden

配置为 true 将在文档中隐藏

response

返回的对象

responseContainer

这些对象是有效的 "List", "Set" or "Map",其他无效

httpMethod

"GET"、"HEAD"、"POST"、"PUT"、"DELETE"、"OPTIONS" and "PATCH"

code

http 的状态码 默认 200

extensions

扩展属性

@ApiImplicitParams和@ApiImplicitParam

@ApiImplicitParam用来注解一个操作方法的调用参数信息,而@ApiImplicitParams则是多个@ApiImplicitParam标签列表的包装器。

属性名称

备注

name 

参数名 

value 

参数的描述 

required 

参数是否必填值为 true 或者 false 

dataType 

参数的数据类型,可以是类名或者原始类型。只作为说明,并没有实际验证。

paramType 

参数的类别,其值:

path 以地址的形式提交数据

query 直接跟参数完成自动映射赋

body 以流的形式提交,仅支持 POST

header 参数在 request headers 里边提交

form 以 form 表单的形式提交 仅支持 POST 

defaultValue

参数的默认值

 

@ApiResponses和@ApiResponse

@ApiResponse用来注解一个操作方法可能的响应类型,而@ApiResponses则是多个@ApiResponse标签列表的包装器。注意,如果只需要使用一个@ApiResponse标签也要包含在@ApiResponses内。且Swagger不允许一个响应代码有多个响应类型。

属性名称

备注

code

返回的HTTP的状态码

message

响应中人类可读的消息

response

默认响应类Void

reference

参考

responseHeaders

一个返回信息中可能提供的headers列表

responseContainer

定义一个包含返回信息的容器。值只能是"List", "Set" or “Map"。任何其他值都将被忽略。

 

@ApiModel和@ApiModelProperty

@ApiModel用来提供Swagger模型的附加信息。@ApiModelProperty用来添加与操作模型属性有关的描述信息。

属性名称

备注

value

属性描述

name

如果配置覆盖属性名称

allowableValues

允许的值

access

可以不配置

notes

当前未被使用

dataType

数据类型

required

是否为必传参数

position

显示的顺序位置

hidden

是否因此

example

举例

readOnly

只读

reference

引用

 

 

Bootstrap Thumbnail Second
MySQL

MySQL is the world's most popular open source database.

GO

Bootstrap Thumbnail Third
算法基础

本书介绍了什么是计算机算法,如何描述它们,以及如何来评估它们。

GO