swagger-codegen 生成代码原理

惠诚
2023-12-01

概述:

swagger-codegen可以通过命令行生成代码(v2.1.5 /Swagger格式),也可以通过web服务生成代码(v3.0.11/OpenAPI格式)。两者只是入口不一样,但实际都是调用的DefaultGenerator.generate()来生成代码。

在整个生成代码的过程中,主要是 OpenAPI/Swagger,不同语言的CodegenConfig实现类(可以继承这个来实现自定义的处理方式),DefaultGenerator三者发挥着重要作用。

  1. OpenAPI/Swagger用于装载代码信息(包括项目版本信息,server,标签,路径,返回参数类等);
  2. CodegenConfig是一个接口,不同语言会有对应的实现类,用于处理数据或者承载代码所共有的信息(比如文件路径,自定义标签信息等);
  3. DefaultGenerator就是生成代码的总指挥官,它会有所有的信息,包括了OpenAPI/Swagger,CodegenConfig所拥有的信息。

v2.1.5源码:https://github.com/swagger-api/swagger-codegen
v3.0.11源码:https://github.com/swagger-api/swagger-codegen/tree/v3.0.11

一.命令行入口

SwaggerCodegen.main:

public class SwaggerCodegen {
    public static void main(String[] args) {
        String version = Version.readVersionFromResources();
        @SuppressWarnings("unchecked")
        Cli.CliBuilder<Runnable> builder =
                Cli.<Runnable>builder("swagger-codegen-cli")
                        .withDescription(
                                String.format(
                                        "Swagger code generator CLI (version %s). More info on swagger.io",
                                        version))
                        .withDefaultCommand(Langs.class)
            //这里有Generate.class,其内部有run方法
                        .withCommands(Generate.class, Meta.class, Langs.class, Help.class,
                                ConfigHelp.class, Validate.class, Version.class);
		//调用上述Generate的run()
        builder.build().parse(args).run();
    }
}

Generate.run:

public void run() {
	......

    //设置代码数据
    if (isNotEmpty(spec)) {
        configurator.setInputSpec(spec);
    }
	//设置语言种类
    if (isNotEmpty(lang)) {
        configurator.setLang(lang);
    }
    //设置各种参数值
    ......
    applySystemPropertiesKvpList(systemProperties, configurator);
    applyInstantiationTypesKvpList(instantiationTypes, configurator);
    applyImportMappingsKvpList(importMappings, configurator);
    applyTypeMappingsKvpList(typeMappings, configurator);
    applyAdditionalPropertiesKvpList(additionalProperties, configurator);
    applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator);
    applyReservedWordsMappingsKvpList(reservedWordsMappings, configurator);
    //转换成ClientOptInput
    final ClientOptInput clientOptInput = configurator.toClientOptInput();

    new DefaultGenerator().opts(clientOptInput).generate();
}

CodegenConfigurator.toClientOptInput:

public ClientOptInput toClientOptInput() {

    Validate.notEmpty(lang, "language must be specified");
    Validate.notEmpty(inputSpec, "input spec must be specified");

    setVerboseFlags();
    setSystemProperties();

    CodegenConfig config = CodegenConfigLoader.forName(lang);

    //设置各种属性
	......

    //处理动态属性至additionalProperties(如:XXXCodegen)
    handleDynamicProperties(config);

    if (isNotEmpty(library)) {
        config.setLibrary(library);
    }

    config.additionalProperties().putAll(additionalProperties);

    //设置对应语言的CodegenConfig
    ClientOptInput input = new ClientOptInput()
            .config(config);

    final List<AuthorizationValue> authorizationValues = AuthParser.parse(auth);

    //读取输入的数据
    Swagger swagger = new SwaggerParser().read(inputSpec, authorizationValues, true);

    input.opts(new ClientOpts())
            .swagger(swagger);

    return input;
}

二.web调用

GeneratorController

package io.swagger.v3.generator.online;

    public ResponseContext generate(RequestContext context, GenerationRequest generationRequest) {
        //预处理一些信息(比如:处理生成代码的路径)
        ......
        //调用生成代码的接口
        ResponseContext responseContext = generate(generationRequest, outputRootFolder, outputContentFolder, outputFile);
        LOGGER.debug("generate end - " + requestLog);
        return responseContext;

    }

    private ResponseContext generate(GenerationRequest generationRequest, File outputRootFolder, File outputContentFolder, File outputFile) {
        //生成器服务,用于配置CodegenConfig等
        GeneratorService generatorService = new GeneratorService();
        try {
            generatorService.generationRequest(generationRequest);
        } catch (Exception e) {
            String msg = "Error processing generation request: " + e.getMessage();
            LOGGER.error(msg, e);
            return new ResponseContext()
                    .status(400)
                    .contentType(MediaType.TEXT_PLAIN)
                    .entity(msg);
        }

        final List<File> files;
        try {
            //1.生成代码!
            files = generatorService.generate();
        } catch (Exception e) {
            String msg = String.format("Error generating `%s` code : %s", generationRequest.getLang(), e.getMessage());
            LOGGER.error(msg, e);
            return new ResponseContext()
                    .status(500)
                    .contentType(MediaType.TEXT_PLAIN)
                    .entity(msg);
        }
       ......
    }

1.生成代码 GeneratorService.generate:也是调用的DefaultGenerator.opts(XXX)和DefaultGenerator.generate()

opts方法主要是配置相关的信息,生成代码还是靠generate方法

public List<File> generate() {
    //v3对应着3.0.11 OpenAPI的代码数据
    if (optsV3 != null) {
        return new DefaultGenerator().opts(optsV3).generate();
    } else if (optsV2 != null) {
         //v2对应着swagger的代码数据
        return new io.swagger.codegen.DefaultGenerator().opts(optsV2).generate();
    }
    throw new RuntimeException("missing opts input");
}

三.总指挥官

这里分析的是3.0.11的DefaultGenerator

DefaultGenerator.generate:

生成各种文件的流程是:处理数据,找到模板,生成文件

public List<File> generate() {

    if (swagger == null || config == null) {
        throw new RuntimeException("missing swagger input or config!");
    }
    //添加一些生成器属性(名字,版本,生成时间等)和测试数据
    configureGeneratorProperties();
    //添加appname,license等信息
    configureSwaggerInfo();

    //处理model里面内联(包括array)
    InlineModelResolver inlineModelResolver = new InlineModelResolver();
    inlineModelResolver.flatten(swagger);

    List<File> files = new ArrayList<File>();
    // 1.生成models
    List<Object> allModels = new ArrayList<Object>();
    generateModels(files, allModels);
    // 2.生成apis
    List<Object> allOperations = new ArrayList<Object>();
    generateApis(files, allOperations, allModels);

    // 3.生成支持文件的数据
    Map<String, Object> bundle = buildSupportFileBundle(allOperations, allModels);
    // 生成支持文件,就是除去api和model所需要的文件
    generateSupportingFiles(files, bundle);
    config.processSwagger(swagger);
    //返回生成的所有文件
    return files;
}

1.生成models

private void generateModels(List<File> files, List<Object> allModels) {

    if (!generateModels) {
        return;
    }

    //获取所有的model数据
    final Map<String, Schema> schemas = this.openAPI.getComponents().getSchemas();
    if (schemas == null) {
        return;
    }
......

    for (String name : modelKeys) {
        try {
            //不需要生成可以导入的类
            if(config.importMapping().containsKey(name)) {
                LOGGER.info("Model " + name + " not imported due to import mapping");
                continue;
            }
            Schema schema = schemas.get(name);
            Map<String, Schema> schemaMap = new HashMap<>();
            schemaMap.put(name, schema);
            //处理model数据(包括了包路径,属性信息)
            Map<String, Object> models = processModels(config, schemaMap, schemas);
            //存放类名
            //toModelName多处使用,为了统一名字格式(非字母下划线数字字符会被去掉)
            models.put("classname", config.toModelName(name));
            models.putAll(config.additionalProperties());
            allProcessedModels.put(name, models);

            final List<Object> modelList  = (List<Object>) models.get("models");

            if (modelList == null || modelList.isEmpty()) {
                continue;
            }

            //处理内部类?
            for (Object object : modelList) {
                Map<String, Object> modelMap = (Map<String, Object>) object;
                CodegenModel codegenModel = null;
                if (modelMap.containsKey("oneOf-model")) {
                    codegenModel = (CodegenModel) modelMap.get("oneOf-model");
                }
                if (modelMap.containsKey("anyOf-model")) {
                    codegenModel = (CodegenModel) modelMap.get("anyOf-model");
                }
                if (codegenModel != null) {
                    models = processModel(codegenModel, config, schemas);
                    models.put("classname", config.toModelName(codegenModel.name));
                    models.putAll(config.additionalProperties());
                    allProcessedModels.put(codegenModel.name, models);
                    break;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not process model '" + name + "'" + ".Please make sure that your schema is correct!", e);
        }
    }

    // 预处理model
    allProcessedModels = config.postProcessAllModels(allProcessedModels);

    for (String modelName: allProcessedModels.keySet()) {
        //获取model信息
        Map<String, Object> models = (Map<String, Object>)allProcessedModels.get(modelName);
        try {
            //不需要生成importMapping中有的类
            if(config.importMapping().containsKey(modelName)) {
                continue;
            }
            //获取模板
            Map<String, Object> modelTemplate = (Map<String, Object>) ((List<Object>) models.get("models")).get(0);
            if (isJavaCodegen(config.getName())) {
                // 特殊处理java别名
                if (modelTemplate != null && modelTemplate.containsKey("model")) {
                    CodegenModel codegenModel = (CodegenModel) modelTemplate.get("model");
                    Map<String, Object> vendorExtensions = codegenModel.getVendorExtensions();
                    boolean isAlias = false;
                    if (vendorExtensions.get(CodegenConstants.IS_ALIAS_EXT_NAME) != null) {
                        isAlias = Boolean.parseBoolean(vendorExtensions.get(CodegenConstants.IS_ALIAS_EXT_NAME).toString());
                    }
                    if (isAlias) {
                        continue;  
                    }
                }
            }
            allModels.add(modelTemplate);
            for (String templateName : config.modelTemplateFiles().keySet()) {
                String suffix = config.modelTemplateFiles().get(templateName);
                String filename = config.modelFileFolder() + File.separator + config.toModelFilename(modelName) + suffix;
                if (!config.shouldOverwrite(filename)) {
                    LOGGER.info("Skipped overwriting " + filename);
                    continue;
                }
                //根据模板生成代码
                File written = processTemplateToFile(models, templateName, filename);
                if(written != null) {
                    files.add(written);
                }
            }
            if(generateModelTests) {
                //生成测试代码
                generateModelTests(files, models, modelName);
            }
            if(generateModelDocumentation) {
                // 生成doc
                generateModelDocumentation(files, models, modelName);
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not generate model '" + modelName + "'", e);
        }
    }
   ......
}

2.生成apis

private void generateApis(List<File> files, List<Object> allOperations, List<Object> allModels) {
    if (!generateApis) {
        return;
    }
    boolean hasModel = true;
    if (allModels == null || allModels.isEmpty()) {
        hasModel = false;
    }

    //获取所有整理过的路径信息
    Map<String, List<CodegenOperation>> paths = processPaths(this.openAPI.getPaths());
    Set<String> apisToGenerate = null;
    String apiNames = System.getProperty("apis");
    if(apiNames != null && !apiNames.isEmpty()) {
        apisToGenerate = new HashSet<String>(Arrays.asList(apiNames.split(",")));
    }
    if(apisToGenerate != null && !apisToGenerate.isEmpty()) {
        Map<String, List<CodegenOperation>> updatedPaths = new TreeMap<>();
        for(String m : paths.keySet()) {
            if(apisToGenerate.contains(m)) {
                updatedPaths.put(m, paths.get(m));
            }
        }
        paths = updatedPaths;
    }
    //生成每个api类
    for (String tag : paths.keySet()) {
        try {
            List<CodegenOperation> ops = paths.get(tag);
            Collections.sort(ops, new Comparator<CodegenOperation>() {
                @Override
                public int compare(CodegenOperation one, CodegenOperation another) {
                    return ObjectUtils.compare(one.operationId, another.operationId);
                }
            });
            //获取api代码所需要的所有信息
            Map<String, Object> operation = processOperations(config, tag, ops, allModels);

            //路径对应OpenAPI的server的url
            operation.put("basePath", basePath);
            //没有host的路径
            operation.put("basePathWithoutHost", basePathWithoutHost);
            operation.put("contextPath", contextPath);
            //标签名字,用于关联path(operation)
            operation.put("baseName", tag);
            //model包路径
            operation.put("modelPackage", config.modelPackage());
            operation.putAll(config.additionalProperties());
            //类名 toApiName会生成驼峰格式的类名
            operation.put("classname", config.toApiName(tag));
            operation.put("classVarName", config.toApiVarName(tag));
            operation.put("importPath", config.toApiImport(tag));
            //类的文件名
            operation.put("classFilename", config.toApiFilename(tag));

            //额外的信息(可以包含自定义信息)
            if(!config.vendorExtensions().isEmpty()) {
                operation.put("vendorExtensions", config.vendorExtensions());
            }

            // Pass sortParamsByRequiredFlag through to the Mustache template...
            boolean sortParamsByRequiredFlag = true;
            if (this.config.additionalProperties().containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) {
                sortParamsByRequiredFlag = Boolean.valueOf(this.config.additionalProperties().get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString());
            }
            operation.put("sortParamsByRequiredFlag", sortParamsByRequiredFlag);

            operation.put("hasModel", hasModel);


            allOperations.add(new HashMap<>(operation));
            for (int i = 0; i < allOperations.size(); i++) {
                Map<String, Object> oo = (Map<String, Object>) allOperations.get(i);
                if (i < (allOperations.size() - 1)) {
                    oo.put("hasMore", "true");
                }
            }

            for (String templateName : config.apiTemplateFiles().keySet()) {
                String filename = config.apiFilename(templateName, tag);
                if (!config.shouldOverwrite(filename) && new File(filename).exists()) {
                    LOGGER.info("Skipped overwriting " + filename);
                    continue;
                }

                //根据模板生成代码
                File written = processTemplateToFile(operation, templateName, filename);
                if(written != null) {
                    files.add(written);
                }
            }

            if(generateApiTests) {
                // 生成api测试文件
                for (String templateName : config.apiTestTemplateFiles().keySet()) {
                    String filename = config.apiTestFilename(templateName, tag);
                    // do not overwrite test file that already exists
                    if (new File(filename).exists()) {
                        LOGGER.info("File exists. Skipped overwriting " + filename);
                        continue;
                    }

                    File written = processTemplateToFile(operation, templateName, filename);
                    if (written != null) {
                        files.add(written);
                    }
                }
            }


            if(generateApiDocumentation) {
                // 生成api doc
                for (String templateName : config.apiDocTemplateFiles().keySet()) {
                    String filename = config.apiDocFilename(templateName, tag);
                    if (!config.shouldOverwrite(filename) && new File(filename).exists()) {
                        LOGGER.info("Skipped overwriting " + filename);
                        continue;
                    }

                    File written = processTemplateToFile(operation, templateName, filename);
                    if (written != null) {
                        files.add(written);
                    }
                }
            }

        } catch (Exception e) {
            throw new RuntimeException("Could not generate api file for '" + tag + "'", e);
        }
    }
 ......
}

3.生成支持文件

private void generateSupportingFiles(List<File> files, Map<String, Object> bundle) {
    if (!generateSupportingFiles) {
        return;
    }
    Set<String> supportingFilesToGenerate = null;
    String supportingFiles = System.getProperty(CodegenConstants.SUPPORTING_FILES);
    boolean generateAll = false;
    if (supportingFiles != null && supportingFiles.equalsIgnoreCase("true")) {
        generateAll = true;
    } else if (supportingFiles != null && !supportingFiles.isEmpty()) {
        supportingFilesToGenerate = new HashSet<>(Arrays.asList(supportingFiles.split(",")));
    }

    for (SupportingFile support : config.supportingFiles()) {
        try {
            //生成文件和模板名字
            ......
            //根据模板生成支持文件
            if(ignoreProcessor.allowsFile(new File(outputFilename))) {
                if (templateFile.endsWith("mustache")) {
                    String rendered = templateEngine.getRendered(templateFile, bundle);
                    writeToFile(outputFilename, rendered);
                    files.add(new File(outputFilename));
                } else {
                    InputStream in = null;

                    try {
                        in = new FileInputStream(templateFile);
                    } catch (Exception e) {
                        // continue
                    }
                    if (in == null) {
                        in = this.getClass().getClassLoader().getResourceAsStream(getCPResourcePath(templateFile));
                    }
                    File outputFile = new File(outputFilename);
                    OutputStream out = new FileOutputStream(outputFile, false);
                    if (in != null) {
                        LOGGER.info("writing file " + outputFile);
                        IOUtils.copy(in, out);
                        out.close();
                    } else {
                        LOGGER.warn("can't open " + templateFile + " for input");
                    }
                    files.add(outputFile);
                }
            } else {
                LOGGER.info("Skipped generation of " + outputFilename + " due to rule in .swagger-codegen-ignore");
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not generate supporting file '" + support + "'", e);
        }
    }

    //需要则生成 .swagger-codegen-ignore 
    final String swaggerCodegenIgnore = ".swagger-codegen-ignore";
    String ignoreFileNameTarget = config.outputFolder() + File.separator + swaggerCodegenIgnore;
    File ignoreFile = new File(ignoreFileNameTarget);
    if (generateSwaggerMetadata && !ignoreFile.exists()) {
        String ignoreFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + swaggerCodegenIgnore;
        String ignoreFileContents = readResourceContents(ignoreFileNameSource);
        try {
            writeToFile(ignoreFileNameTarget, ignoreFileContents);
        } catch (IOException e) {
            throw new RuntimeException("Could not generate supporting file '" + swaggerCodegenIgnore + "'", e);
        }
        files.add(ignoreFile);
    }

    if(generateSwaggerMetadata) {
        //生成swagger元数据
        final String swaggerVersionMetadata = config.outputFolder() + File.separator + ".swagger-codegen" + File.separator + "VERSION";
        File swaggerVersionMetadataFile = new File(swaggerVersionMetadata);
        try {
            writeToFile(swaggerVersionMetadata, ImplementationVersion.read());
            files.add(swaggerVersionMetadataFile);
        } catch (IOException e) {
            throw new RuntimeException("Could not generate supporting file '" + swaggerVersionMetadata + "'", e);
        }
    }

}
 类似资料: