一、Swagger简介

  上一篇文章中我们介绍了Spring Boot对Restful的支持,这篇文章我们继续讨论这个话题,不过,我们这里不再讨论Restful API如何实现,而是讨论Restful API文档的维护问题。

  在日常的工作中,我们往往需要给前端(WEB端、IOS、Android)或者第三方提供接口,这个时候我们就需要给他们提供一份详细的API说明文档。但维护一份详细的文档可不是一件简单的事情。首先,编写一份详细的文档本身就是一件很费时费力的事情,另一方面,由于代码和文档是分离的,所以很容易导致文档和代码的不一致。这篇文章我们就来分享一种API文档维护的方式,即通过Swagger来自动生成Restuful API文档。

  那什么是Swagger?我们可以直接看下官方的描述:

  1. THE WORLD'S MOST POPULAR API TOOLING
  2. Swagger is the world’s largest framework of API developer tools for the OpenAPI Specification(OAS),
  3. enabling development across the entire API lifecycle, from design and documentation, to test and deployment.

  这段话首先告诉大家Swagger是世界上最流行的API工具,并且Swagger的目的是支撑整个API生命周期的开发,包括设计、文档以及测试和部署。这篇文章中我们会用到Swagger的文档管理和测试功能。

  对Swagger的作用有了基本的认识后,我们现在来看看怎么使用。

二、Swagger与Spring boot集成

  第一步:引入对应jar包:

  1. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-swagger2</artifactId>
  4. <version>2.6.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>io.springfox</groupId>
  8. <artifactId>springfox-swagger-ui</artifactId>
  9. <version>2.6.0</version>
  10. </dependency>

  第二步,基本信息配置:

  1. @Configuration
  2. @EnableSwagger2
  3. public class Swagger2Config {
  4. @Bean
  5. public Docket createRestApi() {
  6. return new Docket(DocumentationType.SWAGGER_2)
  7. .apiInfo(apiInfo())
  8. .select()
  9. .apis(RequestHandlerSelectors.basePackage("com.pandy.blog.rest"))
  10. .paths(PathSelectors.regex("/rest/.*"))
  11. .build();
  12. }
  13.  
  14. private ApiInfo apiInfo() {
  15. return new ApiInfoBuilder()
  16. .title("Blog系统Restful API")
  17. .description("Blog系统Restful API")
  18. .termsOfServiceUrl("http://127.0.0.1:8080/")
  19. .contact("liuxiaopeng")
  20. .version("1.0")
  21. .build();
  22. }
  23.  
  24. }

  基础的配置是对整个API文档的描述以及一些全局性的配置,对所有接口起作用。这里涉及到两个注解:

  @Configuration是表示这是一个配置类,是JDK自带的注解,前面的文章中也已做过说明。

  @EnableSwagger2的作用是启用Swagger2相关功能。

  在这个配置类里面我么实例化了一个Docket对象,这个对象主要包括三个方面的信息:

    (1)整个API的描述信息,即ApiInfo对象包括的信息,这部分信息会在页面上展示。

    (2)指定生成API文档的包名。

    (3)指定生成API的路径。按路径生成API可支持四种模式,这个可以参考其源码:

  1. public class PathSelectors {
  2. private PathSelectors() {
  3. throw new UnsupportedOperationException();
  4. }
  5.  
  6. public static Predicate<String> any() {
  7. return Predicates.alwaysTrue();
  8. }
  9.  
  10. public static Predicate<String> none() {
  11. return Predicates.alwaysFalse();
  12. }
  13.  
  14. public static Predicate<String> regex(final String pathRegex) {
  15. return new Predicate<String>() {
  16. public boolean apply(String input) {
  17. return input.matches(pathRegex);
  18. }
  19. };
  20. }
  21.  
  22. public static Predicate<String> ant(final String antPattern) {
  23. return new Predicate<String>() {
  24. public boolean apply(String input) {
  25. AntPathMatcher matcher = new AntPathMatcher();
  26. return matcher.match(antPattern, input);
  27. }
  28. };
  29. }
  30. }

  从源码可以看出,Swagger总共支持任何路径都生成、任何路径都不生成以及正则匹配和ant 模式匹配四种方式。大家可能比较熟悉的是前三种,最后一种ant匹配,如果不熟悉ant的话就直接忽略吧,前三种应该足够大家在日常工作中使用了。

  有了上面的配置我们就可以看到效果了,我在com.pandy.blog.rest这个包下面有一个ArticleRestController这个类,源码如下:

  1. @RestController
  2. public class ArticleRestController {
  3.  
  4. @Autowired
  5. private ArticleService articleService;
  6.  
  7. @RequestMapping(value = "/rest/article", method = POST, produces = "application/json")
  8. public WebResponse<Map<String, Object>> saveArticle(@RequestBody Article article) {
  9. article.setUserId(1L);
  10. articleService.saveArticle(article);
  11. Map<String, Object> ret = new HashMap<>();
  12. ret.put("id", article.getId());
  13. WebResponse<Map<String, Object>> response = WebResponse.getSuccessResponse(ret);
  14. return response;
  15. }
  16.  
  17. @RequestMapping(value = "/rest/article/{id}", method = DELETE, produces = "application/json")
  18. public WebResponse<?> deleteArticle(@PathVariable Long id) {
  19. Article article = articleService.getById(id);
  20. article.setStatus(-1);
  21. articleService.updateArticle(article);
  22. WebResponse<Object> response = WebResponse.getSuccessResponse(null);
  23. return response;
  24. }
  25.  
  26. @RequestMapping(value = "/rest/article/{id}", method = PUT, produces = "application/json")
  27. public WebResponse<Object> updateArticle(@PathVariable Long id, @RequestBody Article article) {
  28. article.setId(id);
  29. articleService.updateArticle(article);
  30. WebResponse<Object> response = WebResponse.getSuccessResponse(null);
  31. return response;
  32. }
  33.  
  34. @RequestMapping(value = "/rest/article/{id}", method = GET, produces = "application/json")
  35. public WebResponse<Article> getArticle(@PathVariable Long id) {
  36. Article article = articleService.getById(id);
  37. WebResponse<Article> response = WebResponse.getSuccessResponse(article);
  38. return response;
  39. }
  40.  
  41. @RequestMapping(value = "/test/{id}", method = GET, produces = "application/json")
  42. public WebResponse<?> getNoApi(){
  43. WebResponse<?> response = WebResponse.getSuccessResponse(null);
  44. return response;
  45. }
  46. }

  启动Spring boot,然后访问:http://127.0.0.1:8080/swagger-ui.html即可看到如下结果:

  这个页面上可以看到,除了最后一个接口/test/{id}外,其他接口都生成对应的文档,最后一个接口因为不满足我们配置的路径——“/rest/.*”,所以没有生成文档。

  我们还可以点进去看一下每一个具体的接口,我们这里以“POST /rest/article”这个接口为例:

  可以看到,Swagger为每一个接口都生成了返回结果和请求参数的示例,并且能直接通过下面的"try it out"进行接口访问,方面大家对接口进行测试。整体上感觉Swagger还是很强大的,配置也比较简单。

三、Swagger API详细配置

  不过大家看到这里肯定会有点疑问:

    第一个问题:这个返回结果和请求参数都没有文字性的描述,这个可不可以配置?

    第二个问题:这个请求参应该是直接根据对象反射出来的结果,但是不是对象的每个属性都是必传的,另外参数的值也不一定满足我们的需求,这个能否配置?

  答案肯定是可以的,现在我们就来解决这两个问题,直接看配置的代码:

  1. package com.pandy.blog.rest;
  2.  
  3. import com.pandy.blog.dto.WebResponse;
  4. import com.pandy.blog.po.Article;
  5. import com.pandy.blog.service.ArticleService;
  6. import io.swagger.annotations.ApiImplicitParam;
  7. import io.swagger.annotations.ApiImplicitParams;
  8. import io.swagger.annotations.ApiOperation;
  9. import io.swagger.annotations.ApiResponse;
  10. import io.swagger.annotations.ApiResponses;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.context.annotation.Profile;
  13. import org.springframework.web.bind.annotation.PathVariable;
  14. import org.springframework.web.bind.annotation.RequestBody;
  15. import org.springframework.web.bind.annotation.RequestMapping;
  16. import org.springframework.web.bind.annotation.RestController;
  17.  
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21.  
  22. import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
  23. import static org.springframework.web.bind.annotation.RequestMethod.GET;
  24. import static org.springframework.web.bind.annotation.RequestMethod.POST;
  25. import static org.springframework.web.bind.annotation.RequestMethod.PUT;
  26.  
  27. @RestController
  28. @RequestMapping("/rest")
  29. public class ArticleRestController {
  30.  
  31. @Autowired
  32. private ArticleService articleService;
  33.  
  34. @RequestMapping(value = "/article", method = POST, produces = "application/json")
  35. @ApiOperation(value = "添加文章", notes = "添加新的文章", tags = "Article",httpMethod = "POST")
  36. @ApiImplicitParams({
  37. @ApiImplicitParam(name = "title", value = "文章标题", required = true, dataType = "String"),
  38. @ApiImplicitParam(name = "summary", value = "文章摘要", required = true, dataType = "String"),
  39. @ApiImplicitParam(name = "status", value = "发布状态", required = true, dataType = "Integer")
  40. })
  41. @ApiResponses({
  42. @ApiResponse(code=200,message="成功",response=WebResponse.class),
  43. })
  44. public WebResponse<Map<String,Object>> saveArticle(@RequestBody Article article){
  45. articleService.saveArticle(article);
  46. Map<String,Object> ret = new HashMap<>();
  47. ret.put("id",article.getId());
  48. WebResponse<Map<String,Object>> response = WebResponse.getSuccessResponse(ret);
  49. return response;
  50. }
  51.  
  52. @ApiOperation(value = "删除文章", notes = "根据ID删除文章", tags = "Article",httpMethod = "DELETE")
  53. @ApiImplicitParams({
  54. @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long")
  55. })
  56. @RequestMapping(value = "/{id}",method = DELETE,produces = "application/json")
  57. public WebResponse<?> deleteArticle(@PathVariable Long id){
  58. Article article = articleService.getById(id);
  59. article.setStatus(-1);
  60. articleService.saveArticle(article);
  61. return WebResponse.getSuccessResponse(new HashMap<>());
  62. }
  63.  
  64. @ApiOperation(value = "获取文章列表", notes = "可以根据标题进行模糊查询", tags = "Article",httpMethod = "GET")
  65. @ApiImplicitParams({
  66. @ApiImplicitParam(name = "title", value = "文章标题", required = false, dataType = "String"),
  67. @ApiImplicitParam(name = "pageSize", value = "每页文章数量", required = false, dataType = "Integer"),
  68. @ApiImplicitParam(name = "pageNum", value = "分页的页码", required = false, dataType = "Integer")
  69. })
  70. @RequestMapping(value = "/article/list", method = GET, produces = "application/json")
  71. public WebResponse<?> listArticles(String title, Integer pageSize, Integer pageNum) {
  72. if (pageSize == null) {
  73. pageSize = 10;
  74. }
  75. if (pageNum == null) {
  76. pageNum = 1;
  77. }
  78. int offset = (pageNum - 1) * pageSize;
  79. List<Article> articles = articleService.getArticles(title, 1L, offset, pageSize);
  80. return WebResponse.getSuccessResponse(articles);
  81. }
  82.  
  83. @ApiOperation(value = "更新文章", notes = "更新文章内容", tags = "Article",httpMethod = "PUT")
  84. @ApiImplicitParams({
  85. @ApiImplicitParam(name = "id", value = "文章ID", required = true, dataType = "Long"),
  86. @ApiImplicitParam(name = "title", value = "文章标题", required = false, dataType = "String"),
  87. @ApiImplicitParam(name = "summary", value = "文章摘要", required = false, dataType = "String"),
  88. @ApiImplicitParam(name = "status", value = "发布状态", required = false, dataType = "Integer")
  89. })
  90. @RequestMapping(value = "/article/{id}", method = PUT, produces = "application/json")
  91. public WebResponse<?> updateArticle(@PathVariable Long id,@RequestBody Article article){
  92. article.setId(id);
  93. articleService.updateArticle(article);
  94. return WebResponse.getSuccessResponse(new HashMap<>());
  95. }
  96.  
  97. }

  我们解释一下代码中几个注解及相关属性的具体作用:

  @ApiOperation,整个接口属性配置:

    value:接口说明,展示在接口列表。

    notes:接口详细说明,展示在接口的详情页。

    tags:接口的标签,相同标签的接口会在一个标签页下展示。

    httpMethod:支持的HTTP的方法。

  @ApiImplicitParams,@ApiImplicitParam的容器,可包含多个@ApiImplicitParam注解

  @ApiImplicitParam,请求参数属性配置:

    name:参数名称

    value:参数说明

    required:是否必须

    dataType:数据类型  

  @ApiResponses,@ApiResponse容器,可以包含多个@ApiResponse注解

  @ApiResponse,返回结果属性配置:

    code:返回结果的编码。

    message:返回结果的说明。

    response:返回结果对应的类。    

  完成以上配置后,我们再看下页面效果:

列表页:

      

  可以看到,现在接口都位于Article这个tag下,并且接口后面也有了我们配置好的说明。我们再看下”POST /rest/article“这个接口的详情页:

  图片太大,只截取了title属性的展示,其他几个参数的类似。我们可以从页面上看到请求参数的说明是有的,不过这不是我们预期的效果,如果我们的参数仅仅是简单类型,这种方式应该没问题,但现在的问题是我们的请求参数是一个对象,那如何配置呢?这就涉及到另外两个注解:@ApiModel和@ApiModelProperty,我们还是先看代码,然后再解释,这样更容易理解:

  1. @ApiModel(value="article对象",description="新增&更新文章对象说明")
  2. public class Article {
  3.  
  4. @Id
  5. @GeneratedValue
  6. @ApiModelProperty(name = "id",value = "文章ID",required = false,example = "1")
  7. private Long id;
  8.  
  9. @ApiModelProperty(name = "title",value = "文章标题",required = true,example = "测试文章标题")
  10. private String title;
  11.  
  12. @ApiModelProperty(name = "summary",value = "文章摘要",required = true,example = "测试文章摘要")
  13. private String summary;
  14.  
  15. @ApiModelProperty(hidden = true)
  16. private Date createTime;
  17.  
  18. @ApiModelProperty(hidden = true)
  19. private Date publicTime;
  20.  
  21. @ApiModelProperty(hidden = true)
  22. private Date updateTime;
  23.  
  24. @ApiModelProperty(hidden = true)
  25. private Long userId;
  26.  
  27. @ApiModelProperty(name = "status",value = "文章发布状态",required = true,example = "1")
  28. private Integer status;
  29.  
  30. @ApiModelProperty(name = "type",value = "文章分类",required = true,example = "1")
  31. private Integer type;
  32. }

  @ApiModel是对整个类的属性的配置:

    value:类的说明

    description:详细描述

  @ApiModelProperty是对具体每个字段的属性配置:

    name:字段名称

    value:字段的说明

    required:是否必须

    example:示例值

    hidden:是否显示

  完成上面的配置后,我们再来看效果:

  现在我们可以看到,字段的说明都已经展示出来,并且,示例中字段的值也变成了我们配置的example属性对应的值了。这样,一份完整的API文档就生成了,并且该文档与代码紧密的联系在一起,而不是隔离的两个部分。除此之外,我们还可以直接通过该文档很方便的进行测试,我们只需要点击Example Value下黄色的框,里面的内容就会自动复制到article对应的value框中,然后在点击“Try it out”就可以发起http请求了。

  点击Try it out后,我们就可以看到返回的结果:

  操作还是很方便的,相比Junit和postman,通过Swagger来测试会更加便捷,当然,Swagger的测试并不能代替单元测试,不过,在联调的时候还是有非常大的作用的。

四、总结

  总体上来说,Swagger的配置还是比较简单的,并且Swagger能够自动帮我们生成文档确实为我们节省了不少工作,对后续的维护也提供了很大的帮助。除此之外,Swagger还能根据配置自动为我们生成测试的数据,并且提供对应的HTTP方法,这对我们的自测和联调工作也有不少的帮助,所以我还是推荐大家在日常的开发中去使用Swagger,应该可以帮助大家在一定程度上提高工作效率的。最后,留一个问题给大家思考吧,就是该文档是可以直接通过页面来访问的,那我们总不能把接口直接暴露在生产环境吧,尤其是要对外提供服务的系统,那我们怎么才能在生产环节中关闭这个功能呢?方法有很多,大家可以自己尝试一下。

Spring Boot实战:集成Swagger2的更多相关文章

  1. Spring Boot实战系列(7)集成Consul配置中心

    本篇主要介绍了 Spring Boot 如何与 Consul 进行集成,Consul 只是服务注册的一种实现,还有其它的例如 Zookeeper.Etcd 等,服务注册发现在微服务架构中扮演这一个重要 ...

  2. Spring Boot中使用Swagger2构建RESTful APIs

    关于 Swagger Swagger能成为最受欢迎的REST APIs文档生成工具之一,有以下几个原因: Swagger 可以生成一个具有互动性的API控制台,开发者可以用来快速学习和尝试API. S ...

  3. Spring Boot实战

    Spring在java EE开发中是实际意义上的标准,但我们在开发Spring的时候可能会遇到以下令人头疼的问题: 1.大量配置文件的定义.2.与第三方软件整合的技术问题. Spring每个版本的退出 ...

  4. spring boot实战(第一篇)第一个案例

    版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   spring boot实战(第一篇)第一个案例 前言 写在前面的话 一直想将spring boot相关内容写成一个系列的 ...

  5. 《Spring Boot实战》笔记(目录)

    目录 目 录第一部分 点睛Spring 4.x第1 章 Spring 基础 .............................................................. ...

  6. spring boot 实战教程

    二八法则 - get more with less Java.spring经过多年的发展,各种技术纷繁芜杂,初学者往往不知道该从何下手.其实开发技术的世界也符合二八法则,80%的场景中只有20%的技术 ...

  7. Spring Boot项目使用Swagger2文档教程

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 前言 Sprin ...

  8. Spring Boot中使用Swagger2构建强大的RESTful API文档

    由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ...

  9. spring boot实战(第十三篇)自动配置原理分析

    前言 spring Boot中引入了自动配置,让开发者利用起来更加的简便.快捷,本篇讲利用RabbitMQ的自动配置为例讲分析下Spring Boot中的自动配置原理. 在上一篇末尾讲述了Spring ...

  10. spring boot实战(第十二篇)整合RabbitMQ

    前言 最近几篇文章将围绕消息中间件RabbitMQ展开,对于RabbitMQ基本概念这里不阐述,主要讲解RabbitMQ的基本用法.Java客户端API介绍.spring Boot与RabbitMQ整 ...

随机推荐

  1. Mybatis+Oracle批处理

    1. 批处理 插入 非常多时候都涉及到一系列数据的插入,通过mybatis的动态sql语句可以非常好的解决问题.当然.oracle已经提供了批插入的语句: insert into students s ...

  2. Spring学习笔记(三)之装配Bean

    除了组件扫描与自动装配之外还有基于Java代码的装配与基于XML的装配. 有一些场景是我们不能用自动装配的,比如我们要给第三方库中的组件装配到我们的应用中,这时自动装配无效,因为自动装配只能扫描本应用 ...

  3. Java基础(四)-异常处理机制及其设计

    本篇主要是记录自己所理解的Java异常处理机制(基于jdk1.7)以及怎么去处理和设计异常.还记得当初学习Java异常这块的时候都没怎么注意它的用途,以为就是简单的处理下异常,我避免程序出现这样错误就 ...

  4. 你为什么还坚持.NET

    C#换什么比较合适? 从TIOBE来看,Java.C++.C.Python都好,对了,还不能忘了JS. Sql Server换什么比较合适? MySql挺好,Oracle也不错,也还有不少选择. 都挺 ...

  5. Intellijidea建javaWeb以及Servlet简单实现

    一.创建并设置javaweb工程1.创建javaweb工程File --> New --> Project... 点击Project后出现如下界面,选择Java Enterprise,选中 ...

  6. Jenkins in OpenCASCADE

    Jenkins in OpenCASCADE eryar@163.com Abstract. Jenkins是一个开源软件项目,是基于Java开发的一个持续集成工具,用于监控持续复制的工作,旨在提供一 ...

  7. Intellij Idea配置MapReduce编程环境

    原文参考地址:http://www点w2bc点com/article/229178 增加内容:question1: Hadoop2以上版本时,在Hadoop2的bin目录下没有winutils.exe ...

  8. Python 爬虫实战(一):使用 requests 和 BeautifulSoup

    Python 基础 我之前写的<Python 3 极简教程.pdf>,适合有点编程基础的快速入门,通过该系列文章学习,能够独立完成接口的编写,写写小东西没问题. requests requ ...

  9. listbox控件使用

    1. 属性列表: SelectionMode    组件中条目的选择类型,即多选(Multiple).单选(Single)    Rows             列表框中显示总共多少行    Sel ...

  10. tensorflow ckpt文件转caffemodel时遇到的坑

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 ...