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,并指定 generatorNamespring,因为我们希望生成的是 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-nullablespringdoc-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

生成的代码结构如下:

OpenAPI generatedCode

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 生成 PetError 两个 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 服务端桩代码。后续还可以用它来 生成客户端代码


原始标题:Open API Server Implementation Using OpenAPI Generator | Baeldung