Swagger Codegen 源码分析

准备

Github 获取源码:

git clone https://github.com/swagger-api/swagger-codegen.git

项目结构:

  • 模板位置:modules/swagger-codegen/src/main/resources
  • 程序入口:modules/swagger-codegen-cli/src/main/java/io/swagger/codegen/SwaggerCodegen.java

运行程序:

generate -l java -i "E:\code\swagger-codegen\api.yaml" -o "E:\temp\out" -c "E:\code\swagger-codegen\config.json"

源码分析

程序入口

使用 Airline 解析命令行参数。

当我们传递 generate -l java -i api.yaml -o "E:\temp\out" 时,parse() 会通过反射创建 io.swagger.codegen.cmd.Generate 类实例。

// io.swagger.codegen.SwaggerCodegen
public class SwaggerCodegen {
    public static void main(String[] args) {
        String version = Version.readVersionFromResources();
        
        // 类型参数表明 parse() 方法返回的类型
        @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)
                        .withCommands(Generate.class, Meta.class, Langs.class, Help.class,
                                ConfigHelp.class, Validate.class, Version.class);

        builder.build().parse(args).run();
    }
}
// io.airlift.airline.DefaultCommandFactory
public class DefaultCommandFactory<T> implements CommandFactory<T> {
    @SuppressWarnings("unchecked")
    @Override
    public T createInstance(Class<?> type) {
        return (T) ParserUtil.createInstance(type); // type: io.swagger.codegen.cmd.Generate
    }
}

命令类

Generate 源码分析

详见 Generate.java

@Command(name = "generate", description = "Generate code with chosen lang")
public class Generate implements Runnable {

    @Option(name = {"-v", "--verbose"})
    private Boolean verbose;

    @Option(name = {"-l", "--lang"}, title = "language", required = true)
    private String lang;

    // ... 省略了其他保存参数的属性

    @Override
    public void run() {
        // 读取 -c conifg.json 文件
        // attempt to read from config file
        CodegenConfigurator configurator = CodegenConfigurator.fromFile(configFile);

        // if a config file wasn't specified or we were unable to read it
        if (configurator == null) {
            // createa a fresh configurator
            configurator = new CodegenConfigurator();
        }

        // 使用命令行参数覆盖 config.json 中的配置
        // now override with any specified parameters
        if (verbose != null) {
            configurator.setVerbose(verbose);
        }

        if (skipOverwrite != null) {
            configurator.setSkipOverwrite(skipOverwrite);
        }

        // ... 省略了其他覆盖配置的代码
         
        if (ignoreImportMappings != null) {
            additionalProperties.add(String.format("%s=%s", CodegenConstants.IGNORE_IMPORT_MAPPING_OPTION, Boolean.parseBoolean(ignoreImportMappings)));
        }

        // 这些也是将参数加到 configurator 中
        applySystemPropertiesKvpList(systemProperties, configurator); // 系统属性
        applyInstantiationTypesKvpList(instantiationTypes, configurator); // 在生成实例代码时使用的实现类,格式:swaggerType=instantionType,示例:array=ArrayList,map=HashMap
        applyImportMappingsKvpList(importMappings, configurator);   // 类和它的导入语句的映射关系,格式:javaType=import, 示例:LocalDate=java.time.LocalDate,LocalDateTime=java.util.LocalDateTime
        applyTypeMappingsKvpList(typeMappings, configurator); // Swagger 类型和 语言类型映射关系,格式:swaggerType=javaType,示例:array=List,map=Map,string=String
        applyAdditionalPropertiesKvpList(additionalProperties, configurator);
        applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator); // 指定的类型作为原始数据类型,下面会详解
        applyReservedWordsMappingsKvpList(reservedWordsMappings, configurator);
        final ClientOptInput clientOptInput = configurator.toClientOptInput(); // 读取 OpenAPI 文档

        new DefaultGenerator().opts(clientOptInput).generate(); // 调用 DefaultGenerator.generate() 生成代码文件
    }
}

使用已有的数据模型而不自动生成该模型

这篇文章 OpenAPI Generator - target external models 有解释 --language-specific-primitives 的作用,但 Swagger CodeGen 的实现似乎没有使用到这个参数来跳过模型生成。该参数的意思是将一些类型加入原始类型列表,这样生成器就不会为其生成数据模型代码。

如果我们想使用一些已有的数据模型而且不想 Swagger CodeGen 为我们生成,可以这样配置:

--ignore-import-mapping=false --import-mappings=Pet=com.xxx.model.Pet

也可以通过设置这个扩展字段跳过模型生成:

// io.swagger.codegen.DefaultGenerator#processModels
protected Map<String, Object> processModels(CodegenConfig config, Map<String, Model> definitions, Map<String, Model> allDefinitions) {
    for (String key : definitions.keySet()) {
        Model mm = definitions.get(key);
        if(mm.getVendorExtensions() !=  null && mm.getVendorExtensions().containsKey("x-codegen-ignore")) {
            // skip this model
            LOGGER.debug("skipping model " + key);
            return null;
        }
    }
}

相关源码:

// io.swagger.codegen.cmd.Generate#run
@Override
public void run() {

    // attempt to read from config file
    CodegenConfigurator configurator = CodegenConfigurator.fromFile(configFile);

    // if a config file wasn't specified or we were unable to read it
    if (configurator == null) {
        // createa a fresh configurator
        configurator = new CodegenConfigurator();
    }

    // ...
    // now override with any specified parameters
    // 设置 ignoreImportMapping
    if (ignoreImportMappings != null) {
        additionalProperties.add(String.format("%s=%s", CodegenConstants.IGNORE_IMPORT_MAPPING_OPTION, Boolean.parseBoolean(ignoreImportMappings)));
    }
    
    // ...
    final ClientOptInput clientOptInput = configurator.toClientOptInput();
    new DefaultGenerator().opts(clientOptInput).generate();
}
// io.swagger.codegen.DefaultGenerator#generateModels
protected void generateModels(List<File> files, List<Object> allModels) {
    for (String name : modelKeys) {
        //don't generate models that have an import mapping
        // 设置不忽略 ImportMapping 并且 ImportMapping 存在该类
        if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(name)) {
            LOGGER.info("Model " + name + " not imported due to import mapping");
            continue;
        }
    }
}

读取 OpenAPI 文档

读取 OpenAPI 文档时,有个地方值得关注,就是读取方法参数类型信息。

// io.swagger.codegen.config.CodegenConfigurator#toClientOptInput
public ClientOptInput toClientOptInput() {
    // 省略其他代码
    // 读取 OpenAPI 文档并转换成 Swagger 对象
    Swagger swagger = new SwaggerParser().read(inputSpec, authorizationValues, parseOptions);

    input.opts(new ClientOpts())
        .swagger(swagger);
}
// io.swagger.parser.SwaggerParser#read(java.lang.String, java.util.List<io.swagger.models.auth.AuthorizationValue>, io.swagger.parser.util.ParseOptions)
public Swagger read(String location, List<AuthorizationValue> auths, ParseOptions options) {
    // ... 省略其他代码
    Swagger output;
    output = (new Swagger20Parser()).read(location, auths);
}
// io.swagger.parser.Swagger20Parser#read(java.lang.String, java.util.List<io.swagger.models.auth.AuthorizationValue>)
public Swagger read(String location, List<AuthorizationValue> auths) throws IOException {
    // ...省略其他代码
    // 从网络或本地读取文件
    location = location.replaceAll("\\\\", "/");
    String data;
    if (location.toLowerCase().startsWith("http")) {
        data = RemoteUrl.urlToString(location, auths);
    } else {
        Path pathpath = Paths.get(location);
    }

    if (Files.exists(path, new LinkOption[0])) {
        data = FileUtils.readFileToString(path.toFile(), "UTF-8");
    } else {
        data = ClasspathHelper.loadFileFromClasspath(location);
    }

    return this.convertToSwagger(data); // 将文件读取成字符串并转换成 Swagger 对象
}

一路上弯弯绕绕:
将 JSON 或 YAML 文件解析成字符串:

// io.swagger.parser.Swagger20Parser#convertToSwagger
private Swagger convertToSwagger(String data) throws IOException {
    // 注:JSON 和 YAML 的关系很密切,YAML 可以理解为 JSON 的超集
    JsonNode rootNode;
    if (data.trim().startsWith("{")) {
        // 解析 JSON 字符串
        ObjectMapper mapper = Json.mapper();
        rootNode = mapper.readTree(data);
    } else {
        // 解析 YAML 字符串
        rootNode = this.deserializeYaml(data);
    }

    JsonNode swaggerNode = rootNode.get("swagger");
    SwaggerDeserializationResult result = (new SwaggerDeserializer()).deserialize(rootNode); // 反序列化 JSON 对象
    Swagger convertValue = result.getSwagger();

    return convertValue;
}
// io.swagger.parser.util.SwaggerDeserializer#deserialize
public SwaggerDeserializationResult deserialize(JsonNode rootNode) {
    SwaggerDeserializationResult result = new SwaggerDeserializationResult();
    SwaggerDeserializer.ParseResult rootParse = new SwaggerDeserializer.ParseResult();
    Swagger swagger = this.parseRoot(rootNode, rootParse); // 解析 JSON 对象
    result.setSwagger(swagger);
    result.setMessages(rootParse.getMessages());
    return result;
}

解析 OpenAPI 文档:

// io.swagger.parser.util.SwaggerDeserializer#parseRoot
// 这个方法就是主要的解析方法,解析 OpenAPI 文档的所有内容,包括 paths, definitions...
public Swagger parseRoot(JsonNode node, SwaggerDeserializer.ParseResult result) {
    String location = "";
    Swagger swagger = new Swagger();

    // 解析接口方法、包括参数类型
    obj = this.getObject("paths", on, true, location, result);
    Map<String, Path> paths = this.paths(obj, "paths", result); // 这里会解析接口方法
    swagger.paths(paths);
    // 解析模型定义
    obj = this.getObject("definitions", on, false, location, result);
    Map<String, Model> definitions = this.definitions(obj, "definitions", result);
    swagger.setDefinitions(definitions);
    // 解析参数对象定义,注意要去接口方法的参数类型区分,详见 OpenAPI 规范
    obj = this.getObject("parameters", on, false, location, result);
    Map<String, Parameter> parameters = new LinkedHashMap();
    swagger.setParameters(parameters);
}

解析 paths 定义:

// io.swagger.parser.util.SwaggerDeserializer#paths
public Map<String, Path> paths(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) {
        Set<String> pathKeys = this.getKeys(obj);
        Iterator var6 = pathKeys.iterator();
        while(true) {
            while(var6.hasNext()) {
                String pathName = (String)var6.next();
                JsonNode pathValue = obj.get(pathName);
                ObjectNode path = (ObjectNode)pathValue;
                Path pathObj = this.path(path, location + ".'" + pathName + "'", result); // 遍历解析接口方法
                List<String> eachPart = new ArrayList();
                Matcher m = Pattern.compile("\\{(.+?)\\}").matcher(pathName);
            }
            return output;
        }
}

解析 path 定义:

// io.swagger.parser.util.SwaggerDeserializer#path
public Path path(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) {
    Path path = new Path();
    // 这里是解析参数对象定义,注意要去接口方法的参数类型区分,详见 OpenAPI 规范
    ArrayNode parameters = this.getArray("parameters", obj, false, location, result);
    path.setParameters(this.parameters(parameters, location, result));
    ObjectNode on = this.getObject("get", obj, false, location, result);
    Operation op;
    // 按 HTTP 方法类型去解析接口方法
    if (on != null) {
        op = this.operation(on, location + "(get)", result); // 解析路径下的 GET 请求接口
        if (op != null) {
            path.setGet(op);
        }
    }

    on = this.getObject("post", obj, false, location, result);
    if (on != null) {
        op = this.operation(on, location + "(post)", result);
        if (op != null) {
            path.setPost(op);
        }
    }
    // ... 省略了其他 HTTP 方法类型

    return path;
}

解析接口方法:

// io.swagger.parser.util.SwaggerDeserializer#operation
// 这个方法是解析接口方法的主要方法
public Operation operation(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) {
    // ... 省略了其他代码
    Operation output = new Operation();
    // 解析接口参数
    ArrayNode parameters = this.getArray("parameters", obj, false, location, result);
    output.setParameters(this.parameters(parameters, location, result)); // 解析并设置方法参数

    return output;
}

解析接口方法参数:

// io.swagger.parser.util.SwaggerDeserializer#parameters
public List<Parameter> parameters(ArrayNode obj, String location, SwaggerDeserializer.ParseResult result) {
    List<Parameter> output = new ArrayList();
    Iterator var5 = obj.iterator();
    while(var5.hasNext()) {
        JsonNode item = (JsonNode)var5.next();
        if (item.getNodeType().equals(JsonNodeType.OBJECT)) {
            Parameter param = this.parameter((ObjectNode)item, location, result); // 遍历并解析接口方法参数
            if (param != null) {
                output.add(param);
            }
        }
    }
    return output;
}

终于来到解析参数类型的地方,请注意注释中的【关键】:

// io.swagger.parser.util.SwaggerDeserializer#parameter
// 解析单个接口方法参数
public Parameter parameter(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) {
    String value = this.getString("in", obj, true, location, result);
    if (value != null) {
        // 注:in 的含义详见 OpenAPI 规范
        // 如果 in 的值是 query, header, path, formatData,则直接读取 type, format 的值
        String type = this.getString("type", obj, false, location, result);
        String format = this.getString("format", obj, false, location, result);
        AbstractSerializableParameter<?> sp = null;
        if ("query".equals(value)) {
            sp = new QueryParameter();
        } else if ("header".equals(value)) {
            sp = new HeaderParameter();
        } else if ("path".equals(value)) {
            sp = new PathParameter();
        } else if ("formData".equals(value)) {
            sp = new FormParameter();
        }

        if (sp != null) {
            String paramType = this.getString("type", obj, true, location, result);
            Map<PropertyId, Object> map = new LinkedHashMap();
            map.put(PropertyId.TYPE, type);
            map.put(PropertyId.FORMAT, format);
            Property prop = PropertyBuilder.build(type, format, map); // 关键:弯弯绕绕终于来到这里,这里将 OpenAPI 定义的类型字符串转为 Java 对象。后面生成日期,Map 相关代码时,会用到这个对象。
            if (prop != null) {
                ((AbstractSerializableParameter)sp).setProperty(prop);
                ObjectNode items = this.getObject("items", obj, false, location, result);
                if (items != null) {
                    Property inner = this.schema((Map)null, items, location, result);
                    ((AbstractSerializableParameter)sp).setItems(inner);
                }
            }
            String collectionFormat = this.getString("collectionFormat", obj, false, location, result);
            ((AbstractSerializableParameter)sp).setCollectionFormat(collectionFormat);
            output = sp;
        } else if ("body".equals(value)) {
            // 如果 in 的值是 body,则需要读取 schmea 定义
            BodyParameter bp = new BodyParameter();
            JsonNode node = obj.get("schema");
            if (node != null && node instanceof ObjectNode) {
                bp.setSchema(this.definition((ObjectNode)node, location, result));
            }
            output = bp;
        }
    return (Parameter)output;
}

Swagger 数据类型:

type format Comments
integer int32 signed 32 bits
integer int64 signed 64 bits (a.k.a long)
number float
number double
string
string byte base64 encoded characters
string binary any sequence of octets
boolean
string date As defined by full-date - RFC3339
string date-time As defined by date-time - RFC3339
string password A hint to UIs to obscure input.
// io.swagger.models.properties.PropertyBuilder.Processor#fromType
// 这里的 Processor 是一个枚举类型,表示 Swagger 数据类型
public static Property build(String type, String format, Map<PropertyId, Object> args) {
    final Processor processor = Processor.fromType(type, format);
    return processor.build(fixedArgs);
}

这里列举了一些 Property 的实现类,可以看到其 OpenAPI 规范的对应书写方式,即 type 和 format 的值:

// io.swagger.models.properties.DateProperty
public class DateProperty extends AbstractProperty implements Property {
    public DateProperty() {
        super.type = "string";
        super.format = "date";
    }
}

// io.swagger.models.properties.DateTimeProperty
public class DateTimeProperty extends AbstractProperty implements Property {
    public DateTimeProperty() {
        super.type = "string";
        super.format = "date-time";
    }
}

如何生成 LocalDate 和 LocalDateTime

OpenAPI 文档:

paths:
  /example:
    get:
      operationId: "example"
      parameters:
        - in: "query"
          name: arg0
          type: string
          format: date
        - in: 'query'
          name: arg1
          type: string
          format: date-time
      responses:
        "200":
          description: "OK"

配置:

--additional-properties="dateLibrary=java8-localdatetime"

相关源码:

// io.swagger.codegen.DefaultGenerator#generate
@Override
public List<File> generate() {
    // ...
    configureGeneratorProperties();
    // ...
}
// io.swagger.codegen.DefaultGenerator#configureGeneratorProperties
protected void configureGeneratorProperties() {
    // ...
    config.processOpts();
    // ...
}
// io.swagger.codegen.languages.SpringCodegen#processOpts
@Override
public void processOpts() {

    // Process java8 option before common java ones to change the default dateLibrary to java8.
    if (additionalProperties.containsKey(JAVA_8)) { // java8
        this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA_8).toString()));
    }
    if (this.java8) {
        additionalProperties.put("javaVersion", "1.8");
        additionalProperties.put("jdk8", "true");
        if (!additionalProperties.containsKey(DATE_LIBRARY)) { // dateLibrary
            setDateLibrary("java8");
        }
    }
    
    // ...
    super.processOpts(); // 调用 AbstractJavaCodegen#processOpts
    // ...
}

根据参数选择日期类型:

// io.swagger.codegen.languages.AbstractJavaCodegen#processOpts
@Override
public void processOpts() {
    super.processOpts();
    // ...
    if (additionalProperties.containsKey(DATE_LIBRARY)) {
        setDateLibrary(additionalProperties.get("dateLibrary").toString());
    } else if (java8Mode) {
        setDateLibrary("java8");
    }

    if ("threetenbp".equals(dateLibrary)) {
        additionalProperties.put("threetenbp", true);
        additionalProperties.put("jsr310", "true");
        typeMapping.put("date", "LocalDate");
        typeMapping.put("DateTime", "OffsetDateTime");
        importMapping.put("LocalDate", "org.threeten.bp.LocalDate");
        importMapping.put("OffsetDateTime", "org.threeten.bp.OffsetDateTime");
    } else if ("joda".equals(dateLibrary)) {
        additionalProperties.put("joda", true);
        typeMapping.put("date", "LocalDate");
        typeMapping.put("DateTime", "DateTime");
        importMapping.put("LocalDate", "org.joda.time.LocalDate");
        importMapping.put("DateTime", "org.joda.time.DateTime");
    } else if (dateLibrary.startsWith("java8")) {
        additionalProperties.put("java8", true);
        additionalProperties.put("jsr310", "true");
        if ("java8-localdatetime".equals(dateLibrary)) { // 选择该配置
            typeMapping.put("date", "LocalDate");
            typeMapping.put("DateTime", "LocalDateTime");
            importMapping.put("LocalDate", "java.time.LocalDate");
            importMapping.put("LocalDateTime", "java.time.LocalDateTime");
        } else if ("java8-instant".equals(dateLibrary)) {
            typeMapping.put("date", "Instant");
            typeMapping.put("DateTime", "Instant");
            importMapping.put("Instant", "java.time.Instant");
        } else {
            typeMapping.put("date", "LocalDate");
            typeMapping.put("DateTime", "OffsetDateTime");
            importMapping.put("LocalDate", "java.time.LocalDate");
            importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
        }
    } else if (dateLibrary.equals("legacy")) {
        additionalProperties.put("legacyDates", true);
    }
}

如何生成 Map

生成 Map<String, String> 类型:

paths:
  /example:
    get:
      operationId: "example"
      consumes:
        - multipart/form-data
      parameters:
        - in: "formData"
          name: arg0
          type: object
          items:
            type: integer
            format: int64
      responses:
        "200":
          description: "example"

生成 Map<String, Pet> 类型,虽然这样写 Swagger 编辑器会报错,但确实只有这样写才会生成想要的代码,原因见下面源码分析:

paths:
  /example:
    get:
      operationId: "example"
      consumes:
        - multipart/form-data
      parameters:
        - in: "formData"
          name: arg0
          type: object
          items:
            $ref: "#/definitions/Pet"
      responses:
        "200":
          description: "example"
definitions:
  Pet:
    type: object
    properties:
      name:
        type: string

相关源码:

解析 OpenAPI 文档:

// io.swagger.parser.util.SwaggerDeserializer#parameter
public Parameter parameter(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) {
    Property prop = PropertyBuilder.build(type, format, map); // 解析参数类型
    if (prop != null) {
        ((AbstractSerializableParameter)sp).setProperty(prop);
        ObjectNode items = this.getObject("items", obj, false, location, result); // 解析 Map 第二个泛型参数,第一泛型参数默认是 String 类型。(可以看到这里读的是 OpenAPI 文档的 items 属性)
        if (items != null) {
            Property inner = this.schema((Map)null, items, location, result);
            ((AbstractSerializableParameter)sp).setItems(inner);
        }
    }
}

将 Swagger 类型转成 Java 类型:

// io.swagger.codegen.DefaultCodegen#fromParameter
// 该方法将 Swagger 类型转成 Java 类型
public CodegenParameter fromParameter(Parameter param, Set<String> imports) {
    if (param instanceof SerializableParameter) { // SerializableParameter 不包括 Body,即请求体
        SerializableParameter qp = (SerializableParameter) param;
        Property property;
        String type = qp.getType();
        if ("array".equals(type)) { // for array parameter
            // ...
        } else if ("object".equals(type)) { // for map parameter
            Property inner = qp.getItems(); // 这里取的是 items,作为 Map 的第二个泛型参数
            if (inner == null) {
                inner = new StringProperty().description("//TODO automatically added by swagger-codegen");
            }
            property = new MapProperty(inner); // 转成 Map 类型
            collectionFormat = qp.getCollectionFormat();
            CodegenProperty pr = fromProperty("inner", inner);
            p.items = pr;
            p.baseType = pr.datatype;
            p.isContainer = true;
            p.isMapContainer = true;
            // recursively add import
            while (pr != null) {
                imports.add(pr.baseType);
                pr = pr.items;
            }
        } else {
            // ...
        }

    }
}

生成代码文件

接下来,看 DefaultGenerator.generate() 方法,详见:DefaultGenerator.java

@Override
public List<File> generate() {

    if (swagger == null || config == null) {
        throw new RuntimeException("missing swagger input or config!");
    }
  
    // 根据系统属性决定是否生成模型文件、接口文件、支持文件,并将这些信息加入 additionalProperties
    configureGeneratorProperties();
    // 将 OpenAPI 文件信息加入 additionalProperties
    configureSwaggerInfo();

    // 生成模型文件、接口文件、支持文件
    List<File> files = new ArrayList<File>();
    // models
    List<Object> allModels = new ArrayList<Object>();
    generateModels(files, allModels);
    // apis
    List<Object> allOperations = new ArrayList<Object>();
    generateApis(files, allOperations, allModels);

    // supporting files
    Map<String, Object> bundle = buildSupportFileBundle(allOperations, allModels);
    generateSupportingFiles(files, bundle);
    config.processSwagger(swagger);
    return files;
}

生成数据模型文件

生成数据模型文件:

  • 如果系统属性配置了 models,则只生成这些数据模型
  • 对数据模型进行排序
  • 如果定义的数据模型是可以导入的类型,则不生成这些数据模型文件
  • 对数据模型进行处理
  • 对于每个模型,都会遍历模型模板并生成响应的模型代码文件
  • 生成测试文件(可选)
  • 生成文档文件(可选)
protected void generateModels(List<File> files, List<Object> allModels) {

    if (!isGenerateModels) {
        return;
    }

    // OpenApi 定义的 Definitions (数据模型)
    final Map<String, Model> definitions = swagger.getDefinitions();
    if (definitions == null) {
        return;
    }

    // 如果指定了系统属性 models,则只生成这些数据模型,将要生成的数据模型放到 modelKeys 中
    String modelNames = System.getProperty("models");
    Set<String> modelsToGenerate = null;
    if (modelNames != null && !modelNames.isEmpty()) {
        modelsToGenerate = new HashSet<String>(Arrays.asList(modelNames.split(",")));
    }

    Set<String> modelKeys = definitions.keySet();
    if (modelsToGenerate != null && !modelsToGenerate.isEmpty()) {
        Set<String> updatedKeys = new HashSet<String>();
        for (String m : modelKeys) {
            if (modelsToGenerate.contains(m)) {
                updatedKeys.add(m);
            }
        }
        modelKeys = updatedKeys;
    }

    // 对数据模型进行排序
    // store all processed models
    Map<String, Object> allProcessedModels = new TreeMap<String, Object>(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            Model model1 = definitions.get(o1);
            Model model2 = definitions.get(o2);
            // ...
    });

    // process models only
    for (String name : modelKeys) {
        try {
            // 如果启用 ImportMapping 并且 ImportMapping 包含该类型,说明可以直接导入,则不生成该数据模型
            //don't generate models that have an import mapping
            if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(name)) {
                LOGGER.info("Model " + name + " not imported due to import mapping");
                continue;
            }
            Model model = definitions.get(name);
            Map<String, Model> modelMap = new HashMap<String, Model>();
            modelMap.put(name, model);
            // 对数据模型进行处理,比如是否忽略该模型和添加一些信息
            Map<String, Object> models = processModels(config, modelMap, definitions);
            if (models != null) {
                models.put("classname", config.toModelName(name));
                models.putAll(config.additionalProperties());
                // 加入 allProcessedModels 的数据模型才会生成代码文件
                allProcessedModels.put(name, models);
            }
        } catch (Exception e) {
            String message = "Could not process model '" + name + "'" + ". Please make sure that your schema is correct!";
            LOGGER.error(message, e);
            throw new RuntimeException(message, e);
        }
    }

    // post process all processed models
    allProcessedModels = config.postProcessAllModels(allProcessedModels);

    final boolean skipAlias = config.getSkipAliasGeneration() != null && config.getSkipAliasGeneration();

    // 生成数据模型文件
    // generate files based on processed models
    for (String modelName : allProcessedModels.keySet()) {
        Map<String, Object> models = (Map<String, Object>) allProcessedModels.get(modelName);
        models.put("modelPackage", config.modelPackage());
        try {
            //don't generate models that have an import mapping
            if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(modelName)) {
                continue;
            }
            Map<String, Object> modelTemplate = (Map<String, Object>) ((List<Object>) models.get("models")).get(0);
            if (skipAlias) {
                // Special handling of aliases only applies to Java
                if (modelTemplate != null && modelTemplate.containsKey("model")) {
                    CodegenModel m = (CodegenModel) modelTemplate.get("model");
                    if (m.isAlias) {
                        continue;  // Don't create user-defined classes for aliases
                    }
                }
            }
            allModels.add(modelTemplate);
            // 遍历模型模板文件并生成对应的模型代码文件
            for (String templateName : config.modelTemplateFiles().keySet()) {
                // 获取文件名
                String filename = config.modelFilename(templateName, modelName);
                if (!config.shouldOverwrite(filename)) {
                    LOGGER.info("Skipped overwriting " + filename);
                    continue;
                }
                // 生成代码文件
                File written = processTemplateToFile(models, templateName, filename);
                if (written != null) {
                    files.add(written);
                }
            }
            // 生成测试文件
            if(isGenerateModelTests) {
                generateModelTests(files, models, modelName);
            }
            // 生成文档文件
            if(isGenerateModelDocumentation) {
                // to generate model documentation files
                generateModelDocumentation(files, models, modelName);
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not generate model '" + modelName + "'", e);
        }
    }
    if (System.getProperty("debugModels") != null) {
        LOGGER.info("############ Model info ############");
        Json.prettyPrint(allModels);
    }

}

生成接口文件

生成接口文件:

  • 对接口进行分组,默认是按接口的 tags 属性分组,spring 可以选择按路径的第一段分组
  • 如果指定了系统属性 apis,只这生成这些接口
  • 遍历每个分组并生成接口文件
protected void generateApis(List<File> files, List<Object> allOperations, List<Object> allModels) {
    if (!isGenerateApis) {
        return;
    }
    // 对接口进行分组,默认是按接口的 tags 属性分组,spring 可以选择按路径的第一段分组
    Map<String, List<CodegenOperation>> paths = processPaths(swagger.getPaths());
    
    // 如果指定了系统属性 apis,只这生成这些接口
    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<String, List<CodegenOperation>>();
        for (String m : paths.keySet()) {
            if (apisToGenerate.contains(m)) {
                updatedPaths.put(m, paths.get(m));
            }
        }
        paths = updatedPaths;
    }
    // 遍历每个分组,并生成接口文件
    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);
                }
            });
            Map<String, Object> operation = processOperations(config, tag, ops, allModels);

            operation.put("hostWithoutBasePath", getHostWithoutBasePath());
            operation.put("basePath", basePath);
            operation.put("basePathWithoutHost", basePathWithoutHost);
            operation.put("contextPath", contextPath);
            operation.put("baseName", tag);
            operation.put("apiPackage", config.apiPackage());
            operation.put("modelPackage", config.modelPackage());
            operation.putAll(config.additionalProperties());
            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);

            processMimeTypes(swagger.getConsumes(), operation, "consumes");
            processMimeTypes(swagger.getProduces(), operation, "produces");

            allOperations.add(new HashMap<String, Object>(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(isGenerateApiTests) {
                // to generate api test files
                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(isGenerateApiDocumentation) {
                // to generate api documentation files
                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);
        }
    }
    if (System.getProperty("debugOperations") != null) {
        LOGGER.info("############ Operation info ############");
        Json.prettyPrint(allOperations);
    }

}

对接口方法进行分组的代码:

public Map<String, List<CodegenOperation>> processPaths(Map<String, Path> paths) {
    Map<String, List<CodegenOperation>> ops = new TreeMap<String, List<CodegenOperation>>();
    for (String resourcePath : paths.keySet()) {
        Path path = paths.get(resourcePath);
        processOperation(resourcePath, "get", path.getGet(), ops, path);
        processOperation(resourcePath, "post", path.getPost(), ops, path);
        // ...
    }
    return ops;
}

protected void processOperation(String resourcePath, String httpMethod, Operation operation, Map<String, List<CodegenOperation>> operations, Path path) {
    config.addOperationToGroup(config.sanitizeTag(tag.getName()), resourcePath, operation, codegenOperation, operations);
}


// SpringCodegen.java
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
    // useTags == false 按路径第一段进行分组
    if((library.equals(DEFAULT_LIBRARY) || library.equals(SPRING_MVC_LIBRARY)) && !useTags) {
        String basePath = resourcePath;
        if (basePath.startsWith("/")) {
            basePath = basePath.substring(1);
        }
        int pos = basePath.indexOf("/");
        if (pos > 0) {
            basePath = basePath.substring(0, pos);
        }

        if (basePath.equals("")) {
            basePath = "default";
        } else {
            co.subresourceOperation = !co.path.isEmpty();
        }
        List<CodegenOperation> opList = operations.get(basePath);
        if (opList == null) {
            opList = new ArrayList<CodegenOperation>();
            operations.put(basePath, opList);
        }
        opList.add(co);
        co.baseName = basePath;
    } else {
        // 默认按 tags 进行分组
        super.addOperationToGroup(tag, resourcePath, operation, co, operations);
    }
}

posted @ 2022-07-09 18:29  廖子博  阅读(1439)  评论(0编辑  收藏  举报