1. 概述
顾名思义,OpenAPI Generator 是一个可以从 OpenAPI 规范中生成代码的工具。它支持生成客户端库、服务端桩代码、文档以及配置文件。
该工具支持多种语言和框架,包括 C++、C#、Java、PHP、Python、Ruby、Scala 等 主流语言。
本文将介绍 如何通过 Maven 插件使用 OpenAPI Generator 来生成一个基于 Spring 的服务端桩代码。
除了 Maven 插件之外,你也可以通过 CLI 或 在线工具 来使用 OpenAPI Generator。
2. YAML 文件
首先,我们需要一个描述 API 的 YAML 文件。这个文件将作为输入,供 OpenAPI Generator 生成服务端代码。
下面是一个 petstore.yml 文件的片段:
openapi: "3.0.0"
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
...
responses:
...
post:
summary: Create a pet
operationId: createPets
...
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
...
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
3. Maven 依赖配置
3.1. OpenAPI Generator 插件
接下来,我们需要添加 OpenAPI Generator 的 Maven 插件依赖:
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.8.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>
${project.basedir}/src/main/resources/petstore.yml
</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>com.baeldung.openapi.api</apiPackage>
<modelPackage>com.baeldung.openapi.model</modelPackage>
<supportingFilesToGenerate>
ApiUtil.java
</supportingFilesToGenerate>
<configOptions>
<delegatePattern>true</delegatePattern>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
这里我们传入了 YAML 文件路径作为 inputSpec
,并指定 generatorName
为 spring
,因为我们希望生成的是 Spring 服务端代码。
apiPackage
:定义生成 API 接口的包名modelPackage
:定义生成数据模型类的包名delegatePattern
设置为true
:表示生成的是接口,我们可以在自定义的@Service
类中实现这些接口
⚠️ 注意:OpenAPI Generator 的配置选项在 CLI、Maven/Gradle 插件、在线工具中是通用的。
3.2. Spring 依赖
由于我们要生成的是 Spring 服务端代码,所以还需要引入以下依赖,以确保生成的代码可以编译运行:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.4.6</version>
</dependency>
</dependencies>
此外,还需要引入 jackson-databind-nullable
和 springdoc-openapi-ui
依赖,用于支持生成代码的 JSON 序列化和文档展示:
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.7.0</version>
</dependency>
4. 生成代码
只需运行以下命令即可生成服务端桩代码:
mvn clean install
生成的代码结构如下:
4.1. API 接口
生成的 apiPackage
包含一个 PetsApi
接口,其中定义了所有接口方法:
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen",
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Validated
@Api(value = "pets", description = "the pets API")
public interface PetsApi {
/**
* GET /pets : List all pets
*
* @param limit How many items to return at one time (max 100) (optional)
* @return A paged array of pets (status code 200)
* or unexpected error (status code 200)
*/
@ApiOperation(value = "List all pets", nickname = "listPets", notes = "",
response = Pet.class, responseContainer = "List", tags={ "pets", })
@ApiResponses(value = { @ApiResponse(code = 200, message = "A paged array of pets",
response = Pet.class, responseContainer = "List"),
@ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
@GetMapping(value = "/pets", produces = { "application/json" })
default ResponseEntity<List<Pet>> listPets(@ApiParam(
value = "How many items to return at one time (max 100)")
@Valid @RequestParam(value = "limit", required = false) Integer limit) {
return getDelegate().listPets(limit);
}
// other generated methods
}
4.2. Delegate 接口
由于我们使用了 delegate 模式,OpenAPI 还会生成一个 PetsApiDelegate
接口,默认返回 501 Not Implemented
:
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen",
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public interface PetsApiDelegate {
/**
* GET /pets : List all pets
*
* @param limit How many items to return at one time (max 100) (optional)
* @return A paged array of pets (status code 200)
* or unexpected error (status code 200)
* @see PetsApi#listPets
*/
default ResponseEntity<List<Pet>> listPets(Integer limit) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"name\" : \"name\", \"id\" : 0, \"tag\" : \"tag\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
// other generated method declarations
}
4.3. Controller 实现
生成的 PetsApiController
类只是简单地注入了 delegate:
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen",
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Controller
@RequestMapping("${openapi.swaggerPetstore.base-path:}")
public class PetsApiController implements PetsApi {
private final PetsApiDelegate delegate;
public PetsApiController(
@org.springframework.beans.factory.annotation.Autowired(required = false) PetsApiDelegate delegate) {
this.delegate = Optional.ofNullable(delegate).orElse(new PetsApiDelegate() {});
}
@Override
public PetsApiDelegate getDelegate() {
return delegate;
}
}
4.4. 数据模型
在 modelPackage
中,会根据 YAML 文件中的 schema 生成 Pet
和 Error
两个 POJO:
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen",
date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public class Pet {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String name;
@JsonProperty("tag")
private String tag;
// constructor
@ApiModelProperty(required = true, value = "")
@NotNull
public Long getId() {
return id;
}
// other getters and setters
// equals, hashcode, and toString methods
}
5. 测试服务端
要让服务端真正工作,只需实现 PetsApiDelegate
接口即可。不过为了演示,我们这里不做具体实现,只做测试。
首先,创建一个 Spring Boot 启动类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.1. 使用 curl 测试
启动服务后,运行以下命令:
curl -I http://localhost:8080/pets/
预期返回:
HTTP/1.1 501
Content-Length: 0
Date: Fri, 26 Mar 2021 17:29:25 GMT
Connection: close
5.2. 集成测试
也可以写一个简单的集成测试:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class OpenApiPetsIntegrationTest {
private static final String PETS_PATH = "/pets/";
@Autowired
private MockMvc mockMvc;
@Test
public void whenReadAll_thenStatusIsNotImplemented() throws Exception {
this.mockMvc.perform(get(PETS_PATH)).andExpect(status().isNotImplemented());
}
@Test
public void whenReadOne_thenStatusIsNotImplemented() throws Exception {
this.mockMvc.perform(get(PETS_PATH + 1)).andExpect(status().isNotImplemented());
}
}
6. Spring Boot 3.0 与 Jakarta 命名空间
✅ 对于 Spring Boot 3.0 之前的项目,上述配置是正常的。但从 Spring Boot 3.0 开始,javax
命名空间被替换为 jakarta
。
因此,要生成兼容 Spring Boot 3.0 的代码,需要在配置中添加:
<configOptions>
// ...
<useSpringBoot3>true</useSpringBoot3>
</configOptions>
或者使用:
<configOptions>
// ...
<useJakartaEe>true</useJakartaEe>
</configOptions>
⚠️ 注意:这两个选项在 Spring Boot 3.0 中是等价的,useSpringBoot3=true
会自动启用 useJakartaEe=true
。
7. 总结
本文介绍了如何使用 OpenAPI Generator 的 Maven 插件,从 YAML 文件中生成 Spring 服务端桩代码。后续还可以用它来 生成客户端代码。