Skip to content

Commit e71b1f6

Browse files
committed
swagger
1 parent 6eeae34 commit e71b1f6

18 files changed

+297
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@
279279
- [Spring Boot 整合 JPA](docs/springboot/jpa.md)
280280
- [Spring Boot 整合 Redis 实现缓存](docs/redis/redis-springboot.md)
281281
- [Spring Boot 整合 MyBatis-Plus AutoGenerator 生成编程喵项目骨架代码](docs/kaiyuan/auto-generator.md)
282+
- [Spring Boot 整合 Swagger-UI 实现在线API文档](docs/springboot/swagger.md)
282283
- [Spring Boot 为什么不需要额外安装Tomcat?](docs/springboot/tomcat.md)
283284

284285
## 辅助工具/轮子

docs/.vuepress/sidebar.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -681,21 +681,25 @@ export const sidebarConfig = defineSidebarConfig({
681681
link: "springboot/initializr",
682682
},
683683
{
684-
text: "整合 MySQL和Druid",
684+
text: "整合MySQL和Druid",
685685
link: "springboot/mysql-druid",
686686
},
687687
{
688-
text: "整合 JPA",
688+
text: "整合JPA",
689689
link: "springboot/jpa",
690690
},
691691
{
692-
text: "整合 Redis 实现缓存",
692+
text: "整合Redis实现缓存",
693693
link: "redis/redis-springboot",
694694
},
695695
{
696-
text: "整合 MyBatis-Plus AutoGenerator",
696+
text: "整合MyBatis-Plus AutoGenerator",
697697
link: "kaiyuan/auto-generator",
698698
},
699+
{
700+
text: "整合Swagger-UI",
701+
link: "springboot/swagger"
702+
},
699703
"springboot/tomcat",
700704
],
701705
},

docs/home.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ headerDepth: 1
277277
- [Spring Boot 整合 JPA](springboot/jpa.md)
278278
- [Spring Boot 整合 Redis 实现缓存](redis/redis-springboot.md)
279279
- [Spring Boot 整合 MyBatis-Plus AutoGenerator生成项目骨架代码](kaiyuan/auto-generator.md)
280+
- [Spring Boot 整合 Swagger-UI 实现在线API文档](springboot/swagger.md)
280281
- [Spring Boot 为什么不需要额外安装Tomcat?](springboot/tomcat.md)
281282

282283
## 辅助工具/轮子

docs/springboot/swagger.md

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
category:
3+
- Java企业级开发
4+
tag:
5+
- Spring Boot
6+
title: Spring Boot整合Swagger-UI实现在线API文档
7+
---
8+
9+
### 关于 Swagger
10+
11+
Swagger 是一个用于生成、描述和调用 RESTful 接口的 Web 服务。
12+
13+
14+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-febf2633-5b02-425b-a513-c8583e14d621.png)
15+
16+
17+
>想要理解RESTful架构的话,可以戳链接查看阮一峰老师的博客:[https://www.ruanyifeng.com/blog/2011/09/restful.html](https://www.ruanyifeng.com/blog/2011/09/restful.html)
18+
19+
换句话说,Swagger 就是将项目中想要暴露的接口展示在页面上,开发者可以直接进行接口调用和测试,能在很大程度上提升开发的效率。
20+
21+
比如说,一个后端程序员写了一个登录接口,想要测试自己写的接口是否符合预期的话,就得先模拟用户登录的行为,包括正常的行为(输入正确的用户名和密码)和异常的行为(输入错误的用户名和密码),这就要命了。
22+
23+
但有了 Swagger 后,可以通过简单的配置生成接口的展示页面,把接口的请求参数、返回结果通过可视化的形式展示出来,并且提供了便捷的测试服务。
24+
25+
- 前端程序员可以通过接口展示页面查看需要传递的请求参数和返回的数据格式,不需要后端程序员再编写接口文档了;
26+
- 后端程序员可以通过接口展示页面测试验证自己的接口是否符合预期,降低了开发阶段的调试成本。
27+
28+
前后端分离就可以很完美的落地了,有没有?
29+
30+
>Swagger 官网地址:[https://swagger.io/](https://swagger.io/)
31+
32+
那在 Swagger 出现之前,局面就比较糟糕。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。
33+
34+
大家都被无情地折磨,痛不堪言。。。
35+
36+
Swagger 定义了一套规范,你只需要按照它的规范去定义接口及接口相关的信息,然后通过 Swagger 衍生出来的一系列工具,就可以生成各种格式的接口文档,甚至还可以生成多种语言的客户端和服务端代码,以及在线接口调试页面等等。
37+
38+
那只要及时更新 Swagger 的描述文件,就可以自动生成接口文档了,做到调用端代码、服务端代码以及接口文档的一致性。
39+
40+
### 整合 Swagger-UI
41+
42+
Swagger-UI 是一套 HTML/CSS/JS 框架,用于渲染 Swagger 文档,以便提供美观的 API 文档界面。
43+
44+
也就是说,Swagger-UI 是 Swagger 提供的一套可视化渲染组件,支持在线导入描述文件和本地部署UI项目。
45+
46+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-9cb36679-f1f7-469e-925e-2e54090f700f.png)
47+
48+
第一步,在 pom.xml 文件中添加 Swagger 的 starter。
49+
50+
```
51+
<dependency>
52+
<groupId>io.springfox</groupId>
53+
<artifactId>springfox-boot-starter</artifactId>
54+
<version>3.0.0</version>
55+
</dependency>
56+
```
57+
58+
咦,不是说添加 Swagger 的依赖吗?怎么添加的是 springfox-boot-starter 呢?
59+
60+
这是因为:
61+
62+
- Swagger 是一种规范。
63+
- springfox-swagger 是一个基于 Spring 生态系统的,Swagger 规范的实现。
64+
- springfox-boot-starter 是 springfox 针对 Spring Boot 项目提供的一个 starter,简化 Swagger 依赖的导入,否则我们就需要在 pom.xml 文件中添加 springfox-swagger、springfox-swagger-ui 等多个依赖。
65+
66+
第二步,添加 Swagger 的 Java 配置。
67+
68+
```java
69+
@Configuration
70+
@EnableOpenApi
71+
public class SwaggerConfig {
72+
@Bean
73+
public Docket docket() {
74+
Docket docket = new Docket(DocumentationType.OAS_30)
75+
.apiInfo(apiInfo()).enable(true)
76+
.select()
77+
//apis: 添加swagger接口提取范围
78+
.apis(RequestHandlerSelectors.basePackage("top.codingmore.controller"))
79+
.paths(PathSelectors.any())
80+
.build();
81+
82+
return docket;
83+
}
84+
85+
private ApiInfo apiInfo() {
86+
return new ApiInfoBuilder()
87+
.title("编程猫实战项目笔记")
88+
.description("编程喵是一个 Spring Boot+Vue 的前后端分离项目")
89+
.contact(new Contact("沉默王二", "https://codingmore.top","www.qing_gee@163.com"))
90+
.version("v1.0")
91+
.build();
92+
}
93+
}
94+
```
95+
96+
1)@Configuration 注解通常用来声明一个 Java 配置类,取代了以往的 xml 配置文件,让配置变得更加的简单和直接。
97+
98+
2)@EnableOpenApi 注解表明开启 Swagger。
99+
100+
3)SwaggerConfig 类中包含了一个 @Bean 注解声明的方法 `docket()`,该方法会被 Spring 的 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,然后添加到 Spring 容器当中。
101+
102+
```java
103+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
104+
ctx.register(AppConfig.class);
105+
ctx.refresh();
106+
MyBean myBean = ctx.getBean(MyBean.class);
107+
```
108+
109+
简单描述一下 Swagger 的配置内容:
110+
111+
- `new Docket(DocumentationType.OAS_30)`,使用 3.0 版本的 Swagger API。OAS 是 OpenAPI Specification 的简称,翻译成中文就是 OpenAPI 说明书,Swagger 遵循的就是这套规范。
112+
- `apiInfo(apiInfo())`,配置 API 文档基本信息,标题、描述、作者、版本等。
113+
- `apis(RequestHandlerSelectors.basePackage("top.codingmore.controller"))` 指定 API 的接口范围为 controller 控制器。
114+
- `paths(PathSelectors.any())` 指定匹配所有的 URL。
115+
116+
第三步,添加控制器类。
117+
118+
```java
119+
@Api(tags = "测试 Swagger")
120+
@RestController
121+
@RequestMapping("/swagger")
122+
public class SwaggerController {
123+
124+
@ApiOperation("测试")
125+
@RequestMapping("/test")
126+
public String test() {
127+
return "沉默王二又帅又丑";
128+
}
129+
}
130+
```
131+
132+
1)@Api注解,用在类上,该注解将控制器标注为一个 Swagger 资源。该注解有 3 个属性:
133+
134+
- tags,具有相同标签的 API 会被归在一组内展示
135+
- value,如果 tags 没有定义,value 将作为 API 的 tags 使用。
136+
- description,已废弃
137+
138+
2)@ApiOperation 注解,用在方法上,描述这个方法是做什么用的。该注解有 4 个属性:
139+
140+
- value 操作的简单说明,长度为120个字母,60个汉字。
141+
- notes 对操作的详细说明。
142+
- httpMethod HTTP请求的动作名,可选值有:"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" and "PATCH"。
143+
- code 默认为200,有效值必须符合标准的HTTP Status Code Definitions。
144+
145+
3)@RestController 注解,用在类上,是@ResponseBody@Controller的组合注解,如果方法要返回 JSON 的话,可省去 @ResponseBody 注解。
146+
147+
4)@RequestMapping 注解,可用在类(父路径)和方法(子路径)上,主要用来定义 API 的请求路径和请求类型。该注解有 6 个属性:
148+
149+
- value,指定请求的实际地址
150+
- method,指定请求的method类型, GET、POST、PUT、DELETE等
151+
- consumes,指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html
152+
- produces,指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
153+
- params,指定request中必须包含某些参数值
154+
- headers,指定request中必须包含某些指定的header值
155+
156+
第四步,启动服务,在浏览器中输入 `http://localhost:8080/swagger-ui/` 就可以访问 Swagger 生成的 API 文档了。
157+
158+
159+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-25187213-723a-4120-8485-06759a509659.png)
160+
161+
点开 get 请求的面板,点击「try it out」再点击「excute」可以查看接口返回的数据。
162+
163+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-0f2b9c42-bae4-4712-be29-3771ab3bd3a8.png)
164+
165+
### 版本不兼容
166+
167+
在 Spring Boot 整合 Swagger 的过程中,我发现一个大 bug,Spring Boot 2.6.7 版本和 springfox 3.0.0 版本不兼容,启动的时候直接就报错了。
168+
169+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-529160e4-aa31-410a-aa04-93e9576322b6.png)
170+
171+
>Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
172+
173+
一路跟踪下来,发现 GitHub 上确认有人在 Spring Boot 仓库下提到了这个 bug。
174+
175+
>[https://github.com/spring-projects/spring-boot/issues/28794](https://github.com/spring-projects/spring-boot/issues/28794)
176+
177+
Spring Boot 说这是 springfox 的 bug。
178+
179+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-d0d336e1-2cba-49f4-bd65-6df7f89a6c9f.png)
180+
181+
追踪过来一看,确实。
182+
183+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-cb3c15e7-ecfd-4e5e-92b7-673acb966a54.png)
184+
185+
有人提到的解决方案是切换到 SpringDoc。
186+
187+
188+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-0597f86d-1188-4fe1-8de8-fdb57c5cd524.png)
189+
190+
这样就需要切换注解 `@Api → @Tag``@ApiOperation(value = "foo", notes = "bar") → @Operation(summary = "foo", description = "bar")`,对旧项目不是很友好,如果是新项目的话,倒是可以直接尝试 SpringDoc。
191+
192+
还有人提出的解决方案是:
193+
194+
- 先将匹配策略调整为 ant-path-matcher(application.yml)。
195+
196+
```
197+
spring:
198+
mvc:
199+
path match:
200+
matching-strategy: ANT_PATH_MATCHER
201+
```
202+
203+
- 然后在 Spring 容器中注入下面这个 bean,可以放在 SwaggerConfig 类中。
204+
205+
```
206+
@Bean
207+
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
208+
return new BeanPostProcessor() {
209+
210+
@Override
211+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
212+
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
213+
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
214+
}
215+
return bean;
216+
}
217+
218+
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
219+
List<T> copy = mappings.stream()
220+
.filter(mapping -> mapping.getPatternParser() == null)
221+
.collect(Collectors.toList());
222+
mappings.clear();
223+
mappings.addAll(copy);
224+
}
225+
226+
@SuppressWarnings("unchecked")
227+
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
228+
try {
229+
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
230+
field.setAccessible(true);
231+
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
232+
} catch (IllegalArgumentException | IllegalAccessException e) {
233+
throw new IllegalStateException(e);
234+
}
235+
}
236+
};
237+
}
238+
```
239+
240+
>解决方案地址:[https://github.com/springfox/springfox/issues/3462](https://github.com/springfox/springfox/issues/3462)
241+
242+
重新编译项目,就会发现错误消失了,我只能说GitHub 仓库的 issue 区都是大神!
243+
244+
查看 Swagger 接口文档,发现一切正常。
245+
246+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-05265d24-5242-48ac-9776-58e72798a545.png)
247+
248+
我只能再强调一次,GitHub 仓库的 issue 区都是大神!大家遇到问题的时候,一定要多到 issue 区看看。
249+
250+
至于为什么要这样做,问题的解决者给出了自己的答案。
251+
252+
253+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-8b3d90d6-4eac-4db8-ab52-69c55078df36.png)
254+
255+
大致的意思就是 springfox 和 Spring 在 pathPatternsCondition 上产生了分歧,这两个步骤就是用来消除这个分歧的。
256+
257+
除此之外,还有另外一种保守的做法,直接将 Spring Boot 的版本回退到更低的版本,比如说 2.4.5。
258+
259+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-41096e72-bd7c-4663-b57e-fbc8506ec1cc.png)
260+
261+
### 小结
262+
263+
Swagger 虽然解决了调用端代码、服务端代码以及接口文档的不一致的问题,但有一说一,Swagger-UI 实在是太丑了。
264+
265+
不仅查看不方便,操作起来不方便。
266+
267+
你比如说,我压根就不需要 OPTIONS、HEAD、PATCH、TRACE 这些 HTTP 请求,它也全部列了出来。
268+
269+
270+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-4b42804a-e88c-4cb0-baa2-874af5297bc5.png)
271+
272+
你比如说测试接口的时候要先点「try it out」
273+
274+
275+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-529b1547-90b8-478b-8e9e-b77c0b9ae855.png)
276+
277+
再点「execute」才能发送请求和查看响应结果。
278+
279+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/springboot/swagger-a1411977-0f4c-4688-97bc-3d48ca8dc32b.png)
280+
281+
所以下一节,我们要在 Swagger 的基础上进行美化,增强接口文档,以便提升我们的工作效率。
282+
283+
284+
### 源码路径
285+
286+
>- 编程喵:[https://github.com/itwanger/coding-more](https://github.com/itwanger/coding-more)
287+
>- codingmore-swagger:[https://github.com/itwanger/codingmore-learning](https://github.com/itwanger/codingmore-learning/tree/main/codingmore-swagger)
303 KB
Loading
533 KB
Loading
276 KB
Loading
418 KB
Loading
50.6 KB
Loading
119 KB
Loading

0 commit comments

Comments
 (0)