1. 引言

本教程将深入探讨 OpenAPI Generator 的自定义选项,重点介绍如何创建新生成器,为基于 Apache Camel 的应用生成 REST Producer 路由。

2. 为什么需要新生成器?

之前的教程中,我们展示了如何自定义现有生成器的模板。但有时会遇到无法使用任何现有生成器的情况,例如需要支持新语言或 REST 框架时。

以 Apache Camel 集成框架为例:当前 OpenAPI Generator 版本仅支持生成 Consumer 路由(接收 REST 请求并转发到中介逻辑)。而当我们需要从路由调用 REST API 时,通常使用 Camel 的 REST 组件:

from(GET_QUOTE)
  .id(GET_QUOTE_ROUTE_ID)
  .to("rest:get:/quotes/{symbol}?outType=com.baeldung.tutorials.openapi.quotes.api.model.QuoteResponse");

这段代码有几个可自动生成的关键点:

  • 从 API 定义推导接口参数
  • 指定输入/输出类型
  • 响应负载验证
  • 跨项目统一的路由和 ID 命名

更重要的是,使用代码生成能确保当 API 演进时,生成代码始终与契约保持同步

3. 创建 OpenAPI 生成器项目

在 OpenAPI 中,自定义生成器本质是实现了 CodegenConfig 接口的 Java 类。首先添加依赖:

<dependency>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator</artifactId>
    <version>7.5.0</version>
    <scope>provided</scope>
</dependency>

最新版本可在 Maven Central 获取

生成器核心通过 JRE 的 Service 机制注册实现类。需在 META-INF/services 下创建文件,文件名为 CodegenConfig 实现类的全限定名(标准 Maven 项目中位于 src/main/resources)。

也可用命令快速生成项目骨架:

mkdir -p target
wget -O target/openapi-generator-cli.jar \
  https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.5.0/openapi-generator-cli-7.5.0.jar
java -jar target/openapi-generator-cli.jar meta \
  -o . -n java-camel-client -p com.baeldung.openapi.generators.camelclient

4. 实现生成器

生成器需实现 CodegenConfig 接口,但该接口有 155 个方法!幸好核心提供了 DefaultCodegen 基类,只需覆盖少数方法即可实现工作生成器

public class JavaCamelClientGenerator extends DefaultCodegen {
    // 按需覆盖方法
}

4.1. 生成器元数据

首先实现 getName()getTag()

  • getName() 返回用户指定的生成器名称,常用三部分标识:目标语言 + REST 框架 + 类型(client/server)
  • getTag() 返回 CodegenType 枚举值,本例为 CLIENT
public String getName() {
    return "java-camel-client";
}

public CodegenType getTag() {
    return CodegenType.CLIENT;
}

4.2. 帮助信息

良好的用户体验需提供清晰的生成器说明,通过 getHelp() 实现:

public String getHelp() {
    return "生成 Camel Producer 路由用于调用 API 操作";
}

4.3. 目标文件夹

生成器会输出多种产物:

  • API 实现(客户端/服务端)
  • API 测试
  • API 文档
  • 模型类
  • 模型测试
  • 模型文档

每种产物类型都有对应方法返回其生成路径,例如:

@Override
public String modelFileFolder() {
    return outputFolder() + File.separator + sourceFolder + 
      File.separator + modelPackage().replace('.', File.separatorChar);
}

@Override
public String apiFileFolder() {
    return outputFolder() + File.separator + sourceFolder + 
      File.separator + apiPackage().replace('.', File.separatorChar);
}

路径值来自命令行或构建工具(Maven/Gradle)传入的配置选项

4.4. 模板位置

每个生成器使用一组模板生成目标文件。内置生成器只能替换模板,而自定义生成器可自由添加新模板

在构造函数中通过 xxxTemplateFiles() 方法注册模板,例如:

public JavaCamelClientGenerator() {
    super(); 
    // ... 其他配置省略
    apiTemplateFiles().put("camel-producer.mustache", ".java");
    // ... 其他配置省略
}

此代码注册 camel-producer.mustache 模板,生成的文件名 = API 名称 + 扩展名(如 .java

还需通过 setTemplateDir() 设置模板基础目录:

setTemplateDir("java-camel-client");

4.5. 配置选项

大多数生成器需要用户配置参数,在构造函数中通过 cliOptions() 注册:

public JavaCamelClientGenerator() {
    // ... 其他配置省略
    cliOptions().add(
      new CliOption(CodegenConstants.API_PACKAGE, CodegenConstants.API_PACKAGE_DESC)
        .defaultValue(apiPackage));
    cliOptions().add(
      new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC)
        .defaultValue(sourceFolder));
}

✅ 优先使用 CodegenConstants 中的标准选项名,提升用户迁移体验

4.6. 处理配置选项

生成器核心在开始生成前调用 processOpts()这是设置生成前状态的最后机会

public void processOpts() {
    super.processOpts();

    if (additionalProperties().containsKey(CodegenConstants.SOURCE_FOLDER)) {
        sourceFolder = ((String) additionalProperties().get(CodegenConstants.SOURCE_FOLDER));
        // ... 源文件夹验证省略
    }
}

⚠️ 当前验证失败只能抛 RuntimeException(如 IllegalArgumentException),会附带冗长堆栈信息,体验不佳。

4.7. 附加文件

可生成与 API/模型无关的文件(如 pom.xmlREADME 等),通过构造函数中 supportingFiles() 添加:

public JavaCamelClientGenerator() {
    // ... 其他配置省略
    supportingFiles().add(new SupportingFile("readme.mustache", "", "README.txt"));
}

SupportingFile 是三元组:模板名、相对输出目录、输出文件名。

4.8. 模板辅助类

默认模板引擎 Mustache 数据处理能力有限(如无字符串操作)。需通过实现 Mustache.Lambda 的辅助类扩展:

protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
    ImmutableMap.Builder<String, Mustache.Lambda> builder = super.addMustacheLambdas();
    return builder
      .put("javaconstant", new JavaConstantLambda())
      .put("path", new PathLambda());
}

在模板中通过 lambda 上下文调用:

{{#lambda.javaconstant}}... 任意 Mustache 内容 ...{{/lambda.javaconstant}}

PathLambda 为例(从 URL 提取路径):

public class PathLambda implements Mustache.Lambda {
    @Override
    public void execute(Template.Fragment fragment, Writer writer) throws IOException {
        String maybeUri = fragment.execute();
        try {
            URI uri = new URI(maybeUri);
            if (uri.getPath() != null) {
                writer.write(uri.getPath());
            } else {
                writer.write("/");
            }
        }
        catch (URISyntaxException e) {
            writer.write(maybeUri); // 非 URI 则原样输出
        }
    }
}

4.9. 模板编写

这是最耗时的部分,但可参考现有模板。本例假设生成 Spring Boot 应用组件,每个 API 生成一个继承 RouteBuilder@Component

每个操作添加一个 "direct" 路由,使用 DSL 定义 REST 目标。完整模板见GitHub,生产环境需补充错误处理、重试策略等。

5. 单元测试

使用 CodegenConfigurator 进行基础功能测试:

public void whenLaunchCodeGenerator_thenSuccess() throws Exception {
    Map<String, Object> opts = new HashMap<>();
    opts.put(CodegenConstants.SOURCE_FOLDER, "src/generated");
    opts.put(CodegenConstants.API_PACKAGE, "test.api");

    CodegenConfigurator configurator = new CodegenConfigurator()
      .setGeneratorName("java-camel-client")
      .setInputSpec("petstore.yaml")
      .setAdditionalProperties(opts)
      .setOutputDir("target/out/java-camel-client");

    ClientOptInput clientOptInput = configurator.toClientOptInput();
    DefaultGenerator generator = new DefaultGenerator();
    generator.opts(clientOptInput)
      .generate();

    File f = new File("target/out/java-camel-client/src/generated/test/api/PetApi.java");
    assertTrue(f.exists());
}

此测试验证生成文件是否出现在预期位置(本例为按 API 标签命名的 Java 文件)。

6. 集成测试

单元测试无法验证生成代码的运行时行为。更可靠的方法是创建专用测试项目,在示例项目中添加 OpenAPI Generator 插件:

<plugins>
    <plugin>
        <groupId>org.openapitools</groupId>
        <artifactId>openapi-generator-maven-plugin</artifactId>
        <version>${openapi-generator.version}</version>
        <configuration>
            <skipValidateSpec>true</skipValidateSpec>
            <inputSpec>${project.basedir}/src/main/resources/api/quotes.yaml</inputSpec>
        </configuration>
        <executions>
            <execution>
                <id>generate-camel-client</id>
                <goals>
                    <goal>generate</goal>
                </goals>
                <configuration>
                    <generatorName>java-camel-client</generatorName>
                    <generateModels>false</generateModels>
                    <configOptions>
                        <apiPackage>com.baeldung.tutorials.openapi.quotes.client</apiPackage>
                        <modelPackage>com.baeldung.tutorials.openapi.quotes.api.model</modelPackage>
                    </configOptions>
                </configuration>
            </execution>
                <!-- 其他执行省略 -->
        </executions>
        <dependencies>
            <dependency>
                <groupId>com.baeldung</groupId>
                <artifactId>openapi-custom-generator</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </plugin>
    <!-- 其他插件省略 -->
</plugins>

关键点:将自定义生成器作为插件依赖,才能在 generatorName 中指定 java-camel-client

由于本生成器不支持模型生成,完整 pom.xml 中添加了第二个执行块使用标准 Java 生成器。使用 Camel 测试支持类验证生成代码:

@SpringBootTest
class ApplicationUnitTest {
    @Autowired
    private FluentProducerTemplate producer;

    @Autowired
    private CamelContext camel;

    @Test
    void whenInvokeGeneratedRoute_thenSuccess() throws Exception {
        AdviceWith.adviceWith(camel, QuotesApi.GET_QUOTE_ROUTE_ID, in -> {
            in.mockEndpointsAndSkip("rest:*");
        });

        Exchange exg = producer.to(QuotesApi.GET_QUOTE)
          .withHeader("symbol", "BAEL")
          .send();
        assertNotNull(exg);
    }
}

7. 总结

本教程展示了创建 OpenAPI 自定义生成器的完整流程,并通过测试项目验证了生成代码的实用性。所有代码可在 GitHub 获取。


原始标题:OpenAPI Custom Generator