大纲
Swagger 整合
本章节所需的案例代码,可以直接从 GitHub 下载对应章节 spring-boot3-12
。
概念介绍
springdoc-openapi
用于 Spring 项目快速自动生成 API 文档,通过在运行时检查应用程序来根据 Spring 配置、类结构和各种注解推断 API 语义,详细介绍请阅读 SpringDoc 官方文档。swagger-ui
可以快速生成实时接口文档,包括自动生成 JSON、YAML 和 HTML 格式 API 的文档,并遵循 OpenAPI 规范,详细介绍请查阅 Swagger 官网、Swagger UI 项目。springdoc-openapi
支持以下功能:- OAuth 2
- OpenAPI 3
- Swagger UI
- GraalVM native images
- SpringBoot v3 (Java 17 & Jakarta EE 9)
- JSR-303,specifically for
@NotNull
,@Min
,@Max
and @Size
springdoc-openapi
的架构图如下:
整合案例
引入依赖
1 2 3 4 5
| <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.2.0</version> </dependency>
|
若使用的是基于 WebFlux 的响应式 Web 开发,则需要改为引入以下依赖
1 2 3 4 5
| <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webflux-ui</artifactId> <version>2.2.0</version> </dependency>
|
访问文档
提示
项目引入上述的 Maven 依赖后,SpringBoot 会自动将 swagger-ui 整合到项目中,一般情况下无需任何配置。
- API 文档将默认以 HTML 格式提供,底层使用的是
swagger-ui
- Swagger UI 的页面可以通过
http://server:port/context-path/swagger-ui.html
访问 - OpenAPI 的 JSON 接口可以通过
http://server:port/context-path/v3/api-docs
访问 - OpenAPI 的接口还支持通过
YAML
格式提供,访问路径是 http://server:port/context-path/v3/api-docs.yaml
自定义配置
1 2 3 4 5 6 7
| springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger.html
springdoc.show-actuator=true
|
常用注解
注解介绍
使用案例
1 2 3 4 5 6 7 8 9 10 11
| @Schema(title = "部门信息") @Data public class Dept {
@Schema(title = "部门id") private Long id;
@Schema(title = "部门名字") private String deptName;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Tag(name = "部门", description = "部门的CRUD") @RestController public class DeptController {
@Autowired DeptService deptService;
@Operation(summary = "查询", description = "按照id查询部门") @GetMapping("/dept/{id}") public Dept getDept(@PathVariable("id") Long id) { return deptService.getDeptById(id); }
@Operation(summary = "查询所有部门") @GetMapping("/depts") public List<Dept> getDept() { return deptService.getDepts(); }
@Operation(summary = "保存部门", description = "必须提交json") @PostMapping("/dept") public String saveDept(@RequestBody Dept dept) { deptService.saveDept(dept); return "ok"; }
@Operation(summary = "按照id删除部门", description = "必须提交json") @DeleteMapping("/dept/{id}") public String deleteDept(@PathVariable("id") @Parameter(description = "部门id") Long id) { deptService.deleteDept(id); return "ok"; }
}
|
Docket 配置
Docket 可用于对多个 API 进行分组。
使用案例
如果有多个 Docket (分组) 存在,配置示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration public class ApiUiConfiguration {
@Bean public GroupedOpenApi empApi() { return GroupedOpenApi.builder().group("员工管理").pathsToMatch("/emp/**", "/emps").build(); }
@Bean public GroupedOpenApi deptApi() { return GroupedOpenApi.builder().group("部门管理").pathsToMatch("/dept/**", "/depts").build(); }
}
|
若只有一个 Docket (分组) 存在,那么可以简化为以下配置内容:
1 2
| springdoc.packagesToScan=package1, package2 springdoc.pathsToMatch=/v1, /api/balance/**
|
配置效果
OpenAPI 配置
使用下述的配置代码,可以指定 Swagger 文档的页面描述内容,如文档标题、License、外部文档链接等。
使用案例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class ApiUiConfiguration {
@Bean public OpenAPI docsOpenAPI() { return new OpenAPI().info( new Info().title("SpringBoot3 CRUD API").description("CRUD 接口文档").version("V0.0.1") .license(new License().name("Apache 2.0").url("https://springdoc.org"))).externalDocs( new ExternalDocumentation().description("Wiki Documentation") .url("https://springshop.wiki.github.org/docs")); }
}
|
配置效果
Springfox 迁移
若项目从 Springfox 迁移到 Swagger,那么需要注意以下的变化。
注解使用变化
原注解 | 现注解 | 作用 |
---|
@Api | @Tag | 描述 Controller |
@ApiIgnore | @Parameter(hidden = true),@Operation(hidden = true),@Hidden | 描述忽略操作 |
@ApiImplicitParam | @Parameter | 描述参数 |
@ApiImplicitParams | @Parameters | 描述参数 |
@ApiModel | @Schema | 描述对象 |
@ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 描述对象属性 |
@ApiModelProperty | @Schema | 描述对象属性 |
@ApiOperation(value = “foo”, notes = “bar”) | @Operation(summary = “foo”, description = “bar”) | 描述方法 |
@ApiParam | @Parameter | 描述参数 |
@ApiResponse(code = 404, message = “foo”) | @ApiResponse(responseCode = “404”, description = “foo”) | 描述响应 |
Docket 配置变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration public class ApiUiConfiguration {
@Bean public Docket publicApi() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.public")) .paths(PathSelectors.regex("/public.*")) .build() .groupName("springshop-public") .apiInfo(apiInfo()); }
@Bean public Docket adminApi() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.admin")) .paths(PathSelectors.regex("/admin.*")) .apis(RequestHandlerSelectors.withMethodAnnotation(Admin.class)) .build() .groupName("springshop-admin") .apiInfo(apiInfo()); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Configuration public class ApiUiConfiguration {
@Bean public GroupedOpenApi publicApi() { return GroupedOpenApi.builder() .group("springshop-public") .pathsToMatch("/public/**") .build(); }
@Bean public GroupedOpenApi adminApi() { return GroupedOpenApi.builder() .group("springshop-admin") .pathsToMatch("/admin/**") .addOpenApiMethodFilter(method -> method.isAnnotationPresent(Admin.class)) .build(); }
}
|
远程过程调用整合
概念介绍
RPC(Remote Procedure Call) 是指远程过程调用,也就是说两台服务器 A,B,一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的函数 / 方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。
- 本地过程调用
a()
方法调用了 b()
方法,这些方法都在同一个 JVM 中运行
- 远程过程调用
- 服务提供者
- 服务消费者
- 服务消费者通过连接服务提供者的服务器进行请求 / 响应交互,来实现调用效果
API 与 SDK
API 与 SDK 的区别:
API
:接口(Application Programming Interface)SDK
:工具包(Software Development Kit)
在开发过程中,往往需要调用别人写的功能
- 如果调用的是内部微服务,可以通过依赖注册中心、OpenFeign 等进行调用
- 如果调用的是外部提供的服务,可以发送 HTTP 请求、或遵循外部协议进行调用
SpringBoot 提供了很多种方式进行远程调用
- 轻量级客户端方式
RestTemplate
: 普通开发WebClient
: 响应式开发Http Interface
: 声明式开发
- Spring Cloud 分布式解决方案方式
- Spring Cloud OpenFeign
- Thrift
- Dubbo
- gRPC
- …
WebClient 整合
SpringBoot 的 WebClient 是非阻塞、响应式 HTTP 客户端,需要是 WebFlux 场景(响应式编程)才可以使用。
历史背景
- 在 Spring 5 之前,如果想要调用其他系统提供的 HTTP 服务,通常可以使用 Spring 提供的 RestTemplate 来访问。不过由于 RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端,因此存在一定的性能瓶颈。
- 根据 Spring 官方文档介绍,在未来的版本中 RestTemplate 可能会被弃用。为了新的替代方案,Spring 官方已在 Spring 5 中引入了 WebClient 作为非阻塞式 (Reactive) HTTP 客户端。
- 由于 WebClient 的请求模式属于异步非阻塞,所以它能够以少量固定的线程处理高并发的 HTTP 请求。
使用介绍
创建 WebClient 非常简单:
WebClient.create()
WebClient.create(String baseUrl)
还可以使用 WebClient.builder()
配置更多参数项:
uriBuilderFactory
: 自定义 UriBuilderFactory ,定义 baseurldefaultUriVariables
: 默认 Uri 变量defaultHeader
: 每个请求的默认 HTTP 头defaultCookie
: 每个请求的默认 CookiedefaultRequest
: 自定义每个请求filter
: 过滤 Client 发送的每个请求exchangeStrategies
: HTTP 消息 reader/writer
自定义clientConnector
: HTTP Client 库设置
与 RestTemplate 相比,WebClient 有如下优势:
- 支持同步和异步方案
- 支持从服务器向上或向下流式传输
- 提供基于 Java 8 Lambdas 的函数 API
- 属于非阻塞、响应式的 HTTP 客户端,并支持更高的并发性能,占用更少的硬件资源
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| WebClient client = WebClient.create("https://example.org");
Mono<ResponseEntity<Person>> result = client.get() .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) .retrieve() .toEntity(Person.class);
WebClient client = WebClient.create("https://example.org");
Mono<Person> result = client.get() .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(Person.class);
Flux<Quote> result = client.get() .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(Quote.class);
Mono<Person> result = client.get() .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> ...) .onStatus(HttpStatus::is5xxServerError, response -> ...) .bodyToMono(Person.class);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Mono<Person> personMono = ... ;
Mono<Void> result = client.post() .uri("/persons/{id}", id) .contentType(MediaType.APPLICATION_JSON) .body(personMono, Person.class) .retrieve() .bodyToMono(Void.class);
Flux<Person> personFlux = ... ;
Mono<Void> result = client.post() .uri("/persons/{id}", id) .contentType(MediaType.APPLICATION_STREAM_JSON) .body(personFlux, Person.class) .retrieve() .bodyToMono(Void.class);
Person person = ... ;
Mono<Void> result = client.post() .uri("/persons/{id}", id) .contentType(MediaType.APPLICATION_JSON) .bodyValue(person) .retrieve() .bodyToMono(Void.class);
|
整合案例
提示
下述案例代码,以调用阿里云的天气预报 API 为例子,演示 SpringBoot 项目如何整合 WebClient。完整的案例代码,可以直接从 GitHub 下载对应章节 spring-boot3-13
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class WeatherService {
public Mono<String> weather(String city) { Map<String, String> params = new HashMap<>(); params.put("area", city);
WebClient client = WebClient.create(); return client.get() .uri("https://ali-weather.showapi.com/area-to-weather-date?area={area}", params) .header("Authorization", "APPCODE 93b7e19861a24c519a7548b17dc16d75") .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(String.class); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController public class WeatherController {
@Autowired private WeatherService weatherService;
@GetMapping("/weather") public Mono<String> weather(@RequestParam(name = "city") String city) { return weatherService.weather(city); }
}
|
Http Interface 整合
SpringBoot 3 内置了声明式 HTTP 客户端,允许通过定义接口的方式,给任意位置发送 HTTP 请求,以此简化 HTTP 的远程调用。由于 Http Interface 的底层是基于 Webflux 的 WebClient 实现的,因此必须是 WebFlux 场景(响应式编程)才可以使用。
整合案例
提示
下述案例代码,以调用阿里云的天气预报 API 为例子,演示 SpringBoot 项目如何整合 Http Interface,可以直接从 GitHub 下载对应章节 spring-boot3-14
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency>
|
- 创建 API 接口,用于定义需要调用的外部 API
1 2 3 4 5 6 7
| @HttpExchange public interface WeatherApi {
@GetExchange(value = "/area-to-weather-date", accept = "application/json") Mono<String> weather(@RequestParam("area") String city);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Configuration public class AliYunApiConfiguration {
@Value("${aliyun.api.baseurl:}") private String baseurl;
@Value("${aliyun.api.appcode:}") private String appCode;
@Bean public HttpServiceProxyFactory httpServiceProxyFactory() { WebClient client = WebClient.builder() .baseUrl(baseurl) .defaultHeader("Authorization", "APPCODE " + appCode) .codecs(clientCodecConfigurer -> { clientCodecConfigurer.defaultCodecs().maxInMemorySize(256 * 1024 * 1024); }).build();
return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build(); }
@Bean public WeatherApi weatherApi(HttpServiceProxyFactory httpServiceProxyFactory) { return httpServiceProxyFactory.createClient(WeatherApi.class); }
}
|
- 创建服务类,通过 API 接口的代理对象发送 HTTP 请求
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class WeatherService {
@Autowired private WeatherApi weatherApi;
public Mono<String> weather(String city) { return weatherApi.weather(city); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController public class WeatherController {
@Autowired private WeatherService weatherService;
@GetMapping("/weather") public Mono<String> weather(@RequestParam(name = "city") String city) { return weatherService.weather(city); }
}
|
1 2
| aliyun.api.baseurl=https://ali-weather.showapi.com aliyun.api.appcode=93b7e19861a24c519a7548b17dc16d75
|