swagger.md 12.8 KB
Newer Older
沉默王二's avatar
swagger  
沉默王二 已提交
1 2 3 4 5 6 7 8
---
category:
  - Java企业级开发
tag:
  - Spring Boot
title: Spring Boot整合Swagger-UI实现在线API文档
---

沉默王二's avatar
沉默王二 已提交
9
## 关于 Swagger
沉默王二's avatar
swagger  
沉默王二 已提交
10 11 12 13

Swagger 是一个用于生成、描述和调用 RESTful 接口的 Web 服务。


沉默王二's avatar
沉默王二 已提交
14
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-febf2633-5b02-425b-a513-c8583e14d621.png)
沉默王二's avatar
swagger  
沉默王二 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39


>想要理解RESTful架构的话,可以戳链接查看阮一峰老师的博客:[https://www.ruanyifeng.com/blog/2011/09/restful.html](https://www.ruanyifeng.com/blog/2011/09/restful.html)

换句话说,Swagger 就是将项目中想要暴露的接口展示在页面上,开发者可以直接进行接口调用和测试,能在很大程度上提升开发的效率。

比如说,一个后端程序员写了一个登录接口,想要测试自己写的接口是否符合预期的话,就得先模拟用户登录的行为,包括正常的行为(输入正确的用户名和密码)和异常的行为(输入错误的用户名和密码),这就要命了。

但有了 Swagger 后,可以通过简单的配置生成接口的展示页面,把接口的请求参数、返回结果通过可视化的形式展示出来,并且提供了便捷的测试服务。

- 前端程序员可以通过接口展示页面查看需要传递的请求参数和返回的数据格式,不需要后端程序员再编写接口文档了;
- 后端程序员可以通过接口展示页面测试验证自己的接口是否符合预期,降低了开发阶段的调试成本。

前后端分离就可以很完美的落地了,有没有?

>Swagger 官网地址:[https://swagger.io/](https://swagger.io/)

那在 Swagger 出现之前,局面就比较糟糕。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。

大家都被无情地折磨,痛不堪言。。。

Swagger 定义了一套规范,你只需要按照它的规范去定义接口及接口相关的信息,然后通过 Swagger 衍生出来的一系列工具,就可以生成各种格式的接口文档,甚至还可以生成多种语言的客户端和服务端代码,以及在线接口调试页面等等。

那只要及时更新 Swagger 的描述文件,就可以自动生成接口文档了,做到调用端代码、服务端代码以及接口文档的一致性。

沉默王二's avatar
沉默王二 已提交
40
## 整合 Swagger-UI
沉默王二's avatar
swagger  
沉默王二 已提交
41 42 43 44 45

Swagger-UI 是一套 HTML/CSS/JS 框架,用于渲染 Swagger 文档,以便提供美观的 API 文档界面。

也就是说,Swagger-UI 是 Swagger 提供的一套可视化渲染组件,支持在线导入描述文件和本地部署UI项目。

沉默王二's avatar
沉默王二 已提交
46
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-9cb36679-f1f7-469e-925e-2e54090f700f.png)
沉默王二's avatar
swagger  
沉默王二 已提交
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

第一步,在 pom.xml 文件中添加 Swagger 的 starter。

```
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
```

咦,不是说添加 Swagger 的依赖吗?怎么添加的是 springfox-boot-starter 呢?

这是因为:

- Swagger 是一种规范。
- springfox-swagger 是一个基于 Spring 生态系统的,Swagger 规范的实现。
- springfox-boot-starter 是 springfox 针对 Spring Boot 项目提供的一个 starter,简化 Swagger 依赖的导入,否则我们就需要在 pom.xml 文件中添加 springfox-swagger、springfox-swagger-ui 等多个依赖。

第二步,添加 Swagger 的 Java 配置。

```java
@Configuration
@EnableOpenApi
public class SwaggerConfig {
    @Bean
    public Docket docket() {
        Docket docket = new Docket(DocumentationType.OAS_30)
                .apiInfo(apiInfo()).enable(true)
                .select()
                //apis: 添加swagger接口提取范围
                .apis(RequestHandlerSelectors.basePackage("top.codingmore.controller"))
                .paths(PathSelectors.any())
                .build();

        return docket;
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("编程猫实战项目笔记")
                .description("编程喵是一个 Spring Boot+Vue 的前后端分离项目")
                .contact(new Contact("沉默王二", "https://codingmore.top","www.qing_gee@163.com"))
                .version("v1.0")
                .build();
    }
}
```

1)@Configuration 注解通常用来声明一个 Java 配置类,取代了以往的 xml 配置文件,让配置变得更加的简单和直接。

2)@EnableOpenApi 注解表明开启 Swagger。

3)SwaggerConfig 类中包含了一个 @Bean 注解声明的方法 `docket()`,该方法会被 Spring 的 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,然后添加到 Spring 容器当中。

```java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
  ctx.register(AppConfig.class);
  ctx.refresh();
  MyBean myBean = ctx.getBean(MyBean.class);
```

简单描述一下 Swagger 的配置内容:

- `new Docket(DocumentationType.OAS_30)`,使用 3.0 版本的 Swagger API。OAS 是 OpenAPI Specification 的简称,翻译成中文就是 OpenAPI 说明书,Swagger 遵循的就是这套规范。
- `apiInfo(apiInfo())`,配置 API 文档基本信息,标题、描述、作者、版本等。
- `apis(RequestHandlerSelectors.basePackage("top.codingmore.controller"))` 指定 API 的接口范围为 controller 控制器。
- `paths(PathSelectors.any())` 指定匹配所有的 URL。

第三步,添加控制器类。

```java
@Api(tags = "测试 Swagger")
@RestController
@RequestMapping("/swagger")
public class SwaggerController {

    @ApiOperation("测试")
    @RequestMapping("/test")
    public String test() {
        return "沉默王二又帅又丑";
    }
}
```

1)@Api注解,用在类上,该注解将控制器标注为一个 Swagger 资源。该注解有 3 个属性:

- tags,具有相同标签的 API 会被归在一组内展示
- value,如果 tags 没有定义,value 将作为 API 的 tags 使用。
- description,已废弃

2)@ApiOperation 注解,用在方法上,描述这个方法是做什么用的。该注解有 4 个属性:

- value 操作的简单说明,长度为120个字母,60个汉字。
- notes 对操作的详细说明。
- httpMethod HTTP请求的动作名,可选值有:"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" and "PATCH"。
- code 默认为200,有效值必须符合标准的HTTP Status Code Definitions。

3)@RestController 注解,用在类上,是@ResponseBody和@Controller的组合注解,如果方法要返回 JSON 的话,可省去 @ResponseBody 注解。

4)@RequestMapping 注解,可用在类(父路径)和方法(子路径)上,主要用来定义 API 的请求路径和请求类型。该注解有 6 个属性:

- value,指定请求的实际地址
- method,指定请求的method类型, GET、POST、PUT、DELETE等
- consumes,指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html
- produces,指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
- params,指定request中必须包含某些参数值
- headers,指定request中必须包含某些指定的header值

第四步,启动服务,在浏览器中输入 `http://localhost:8080/swagger-ui/` 就可以访问 Swagger 生成的 API 文档了。


沉默王二's avatar
沉默王二 已提交
159
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-25187213-723a-4120-8485-06759a509659.png)
沉默王二's avatar
swagger  
沉默王二 已提交
160 161 162

点开 get 请求的面板,点击「try it out」再点击「excute」可以查看接口返回的数据。

沉默王二's avatar
沉默王二 已提交
163
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-0f2b9c42-bae4-4712-be29-3771ab3bd3a8.png)
沉默王二's avatar
swagger  
沉默王二 已提交
164

沉默王二's avatar
沉默王二 已提交
165
## 版本不兼容
沉默王二's avatar
swagger  
沉默王二 已提交
166 167 168

在 Spring Boot 整合 Swagger 的过程中,我发现一个大 bug,Spring Boot 2.6.7 版本和 springfox 3.0.0 版本不兼容,启动的时候直接就报错了。

沉默王二's avatar
沉默王二 已提交
169
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-529160e4-aa31-410a-aa04-93e9576322b6.png)
沉默王二's avatar
swagger  
沉默王二 已提交
170 171 172 173 174 175 176 177 178

>Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null

一路跟踪下来,发现 GitHub 上确认有人在 Spring Boot 仓库下提到了这个 bug。

>[https://github.com/spring-projects/spring-boot/issues/28794](https://github.com/spring-projects/spring-boot/issues/28794)

Spring Boot 说这是 springfox 的 bug。

沉默王二's avatar
沉默王二 已提交
179
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-d0d336e1-2cba-49f4-bd65-6df7f89a6c9f.png)
沉默王二's avatar
swagger  
沉默王二 已提交
180 181 182

追踪过来一看,确实。

沉默王二's avatar
沉默王二 已提交
183
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-cb3c15e7-ecfd-4e5e-92b7-673acb966a54.png)
沉默王二's avatar
swagger  
沉默王二 已提交
184 185 186 187

有人提到的解决方案是切换到 SpringDoc。


沉默王二's avatar
沉默王二 已提交
188
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-0597f86d-1188-4fe1-8de8-fdb57c5cd524.png)
沉默王二's avatar
swagger  
沉默王二 已提交
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

这样就需要切换注解 `@Api → @Tag``@ApiOperation(value = "foo", notes = "bar") → @Operation(summary = "foo", description = "bar")`,对旧项目不是很友好,如果是新项目的话,倒是可以直接尝试 SpringDoc。

还有人提出的解决方案是:

- 先将匹配策略调整为 ant-path-matcher(application.yml)。

```
spring:
  mvc:
    path match:
      matching-strategy: ANT_PATH_MATCHER
```

- 然后在 Spring 容器中注入下面这个 bean,可以放在 SwaggerConfig 类中。

```
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
            }
            return bean;
        }

        private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
            List<T> copy = mappings.stream()
                    .filter(mapping -> mapping.getPatternParser() == null)
                    .collect(Collectors.toList());
            mappings.clear();
            mappings.addAll(copy);
        }

        @SuppressWarnings("unchecked")
        private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
            try {
                Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                field.setAccessible(true);
                return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}
```

>解决方案地址:[https://github.com/springfox/springfox/issues/3462](https://github.com/springfox/springfox/issues/3462)

重新编译项目,就会发现错误消失了,我只能说GitHub 仓库的 issue 区都是大神!

查看 Swagger 接口文档,发现一切正常。

沉默王二's avatar
沉默王二 已提交
246
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-05265d24-5242-48ac-9776-58e72798a545.png)
沉默王二's avatar
swagger  
沉默王二 已提交
247 248 249 250 251 252

我只能再强调一次,GitHub 仓库的 issue 区都是大神!大家遇到问题的时候,一定要多到 issue 区看看。

至于为什么要这样做,问题的解决者给出了自己的答案。


沉默王二's avatar
沉默王二 已提交
253
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-8b3d90d6-4eac-4db8-ab52-69c55078df36.png)
沉默王二's avatar
swagger  
沉默王二 已提交
254 255 256 257 258

大致的意思就是 springfox 和 Spring 在 pathPatternsCondition 上产生了分歧,这两个步骤就是用来消除这个分歧的。

除此之外,还有另外一种保守的做法,直接将 Spring Boot 的版本回退到更低的版本,比如说 2.4.5。

沉默王二's avatar
沉默王二 已提交
259
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-41096e72-bd7c-4663-b57e-fbc8506ec1cc.png)
沉默王二's avatar
swagger  
沉默王二 已提交
260

沉默王二's avatar
沉默王二 已提交
261
## 小结
沉默王二's avatar
swagger  
沉默王二 已提交
262 263 264

Swagger 虽然解决了调用端代码、服务端代码以及接口文档的不一致的问题,但有一说一,Swagger-UI 实在是太丑了。

沉默王二's avatar
沉默王二 已提交
265
----
沉默王二's avatar
swagger  
沉默王二 已提交
266

沉默王二's avatar
沉默王二 已提交
267
更多内容,只针对《二哥的Java进阶之路》星球用户开放,需要的小伙伴可以[戳链接🔗](https://javabetter.cn/zhishixingqiu/)加入我们的星球,一起学习,一起卷。。**编程喵**🐱是一个 Spring Boot+Vue 的前后端分离项目,融合了市面上绝大多数流行的技术要点。通过学习实战项目,你可以将所学的知识通过实践进行检验、你可以拓宽自己的技术边界,你可以掌握一个真正的实战项目是如何从 0 到 1 的。
沉默王二's avatar
swagger  
沉默王二 已提交
268

沉默王二's avatar
沉默王二 已提交
269
----
沉默王二's avatar
swagger  
沉默王二 已提交
270 271


沉默王二's avatar
沉默王二 已提交
272
## 源码路径
沉默王二's avatar
swagger  
沉默王二 已提交
273 274 275

>- 编程喵:[https://github.com/itwanger/coding-more](https://github.com/itwanger/coding-more)
>- codingmore-swagger:[https://github.com/itwanger/codingmore-learning](https://github.com/itwanger/codingmore-learning/tree/main/codingmore-swagger)
沉默王二's avatar
沉默王二 已提交
276

沉默王二's avatar
沉默王二 已提交
277
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)