用Swagger生成接口,pom中少了一个library参数,排查了几个小时

news2024/11/27 0:25:49

前言:

        我们一般都会使用swagger生成restful接口,这样可以省不少时间,将更多的精力专注于写业务上。但接口也不是经常写,所以,swagger用的也不熟练。尤其是我喜欢拿之前的接口copy一份,然后在此基础上进行修改,一般情况下,都是能跑通的。不巧,这次并没有成功,原因在于我把服务端的接口拿来改成客户端的接口,当我沉浸于各种copy,delete的操作时,也给自己埋了一个大坑。导致我白白浪费了几个小时去找原因,最后发现是pom.xml文件中少了这一句:

 <library>jersey2</library>

为了了解library的流程,我还去大致浏览了swagger-codegen的源码。因此,不想让自己的时间白白地浪费了,特意写下这篇文章,算是当作笔记。

一、错误重现

如果不加library这个属性,编译接口时,会报如下错误:

[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[16,27] package com.squareup.okhttp does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[17,27] package com.squareup.okhttp does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[21,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[22,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[23,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[24,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[25,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[27,42] cannot find symbol
  symbol: class RequestBody
...

错误较多,只截取了前面一部分,通过这点错误信息,也足够说明问题了。

上面的错误提示找不到xxx的package,查看生成的源码ProgressRequestBody.java的内容:

package com.test.dhcp;

import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.RequestBody;

import java.io.IOException;

import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;

public class ProgressRequestBody extends RequestBody {

    public interface ProgressRequestListener {
        void onRequestProgress(long bytesWritten, long contentLength, boolean done);
    }

    private final RequestBody requestBody;

    private final ProgressRequestListener progressListener;

    public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
        this.requestBody = requestBody;
        this.progressListener = progressListener;
    }

    @Override
    public MediaType contentType() {
        return requestBody.contentType();
    }

    @Override
    public long contentLength() throws IOException {
        return requestBody.contentLength();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink bufferedSink = Okio.buffer(sink(sink));
        requestBody.writeTo(bufferedSink);
        bufferedSink.flush();
    }

    private Sink sink(Sink sink) {
        return new ForwardingSink(sink) {

            long bytesWritten = 0L;
            long contentLength = 0L;

            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                if (contentLength == 0) {
                    contentLength = contentLength();
                }

                bytesWritten += byteCount;
                progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
            }
        };
    }
}

从源码中可看到,ProgressRequestBody.java确实引入了com.squareup.okhttp和okio,pom.xml里面的依赖包如下:

<dependencies>

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>dhcp-api-model</artifactId>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
        </dependency>

        <!-- HTTP client: jersey-client -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-multipart</artifactId>
        </dependency>

        <!-- JSON processing: jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-base</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-json-provider</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.joschi.jackson</groupId>
            <artifactId>jackson-datatype-threetenbp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.brsanthu</groupId>
            <artifactId>migbase64</artifactId>
        </dependency>

    </dependencies>

确实没有引入报错的依赖包OkHttp,所以必然报错。但之前的接口文件,也是这些依赖,为什么他们就能编译通过,也不依赖这个包呢,就此,我开始了爬坑之路。

二、爬坑过程

        既然之前的接口文件编译都没有问题,那么,肯定是我修改的配置文件有问题了,就从这里开始入手,一步步排查。中间做了很多无用功,就不再描述了,总之,一直忽略了细节,找错了方向。最后拿之前的客户端接口文件和当前修改的文件进行一一对比,折腾了半天才发现是少了library那一行,真是一个粗心,在小小的配置文件里,挖了一个大大的坑,白白在坑里耗了那么长时间。为了一探究竟,加深印象,所以就浏览了下swagger-codegen的源码。

三、阅读源码

        根据pom.xml中配置的plugin,找到swagger-codegen的子模块:swagger-codegen-maven-plugin,该模块只有一个源码,如下图所示:

 CodeGenMojo.java的执行入口是execute()方法,我们就从该方法进入,以下是源码,不相关的代码都被删掉了:

@Override
    public void execute() throws MojoExecutionException {
        // Using the naive approach for achieving thread safety
        synchronized (CodeGenMojo.class) {
            execute_();
        }
    }

    protected void execute_() throws MojoExecutionException {

        if (skip) {
            getLog().info("Code generation is skipped.");
            // Even when no new sources are generated, the existing ones should
            // still be compiled if needed.
            addCompileSourceRootIfConfigured();
            return;
        }

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

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

        ......

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

        ......

        final ClientOptInput input = configurator.toClientOptInput();
        final CodegenConfig config = input.getConfig();


        try {
            new DefaultGenerator().opts(input).generate();
        } catch (Exception e) {
            // Maven logs exceptions thrown by plugins only if invoked with -e
            // I find it annoying to jump through hoops to get basic diagnostic information,
            // so let's log it in any case:
            getLog().error(e);
            throw new MojoExecutionException(
                    "Code generation failed. See above for the full exception.");
        }

        addCompileSourceRootIfConfigured();
    }

从上面的源码可知,如果library属性不为空,则将其赋值给configurator对象:

configurator.setLibrary(library);

configurator对象初始化过程:

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

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

因为并未指定configurationFile,所以library的值被赋值给CodegenConfigurator创建的configurator对象。

继续往下执行代码:

        final ClientOptInput input = configurator.toClientOptInput();
        final CodegenConfig config = input.getConfig();

这两行代码,是将CodegenConfigurator创建的configurator对象中的配置参数都赋值给CodegenConfig对象config。

继续往下执行,这行代码就是生成源码的入口了:

new DefaultGenerator().opts(input).generate();

我们先看下DefaultGenerator.opts()方法:

    public Generator opts(ClientOptInput opts) {
        this.opts = opts;
        this.swagger = opts.getSwagger();
        this.config = opts.getConfig();
        this.config.additionalProperties().putAll(opts.getOpts().getProperties());

        String ignoreFileLocation = this.config.getIgnoreFilePathOverride();
        if (ignoreFileLocation != null) {
            final File ignoreFile = new File(ignoreFileLocation);
            if (ignoreFile.exists() && ignoreFile.canRead()) {
                this.ignoreProcessor = new CodegenIgnoreProcessor(ignoreFile);
            } else {
                LOGGER.warn("Ignore file specified at {} is not valid. This will fall back to an existing ignore file if present in the output directory.", ignoreFileLocation);
            }
        }

        if (this.ignoreProcessor == null) {
            this.ignoreProcessor = new CodegenIgnoreProcessor(this.config.getOutputDir());
        }

        return this;
    }

以上代码主要是赋值和初始化,接着跟踪DefaultGenerator.generate()方法:

    public List<File> generate() {

        if (swagger == null || config == null) {
            throw new RuntimeException("missing swagger input or config!");
        }
        configureGeneratorProperties();
        configureSwaggerInfo();

        // resolve inline models
        InlineModelResolver inlineModelResolver = new InlineModelResolver();
        inlineModelResolver.flatten(swagger);

        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;
    }

上面的代码,就是生成model和apis的地方,且各自单独用一个方法实现。这里我们就不跟踪models的生成方法了,仅跟踪apis的生成方法generateApis():

protected void generateApis(List<File> files, List<Object> allOperations, List<Object> allModels) {
        if (!isGenerateApis) {
            return;
        }
        Map<String, List<CodegenOperation>> paths = processPaths(swagger.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<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);
        }

    }

上面是完整的代码,我们需要重点关注下面这一段:



                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);
                    }
                }

注意看,for循环里面就是根据名字去获取模板路径,然后将路径传递给processTemplateToFile()方法,跟踪代码:

    protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
        String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
        if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) {
            String templateFile = getFullTemplateFile(config, templateName);
            String template = readTemplate(templateFile);
            Mustache.Compiler compiler = Mustache.compiler();
            compiler = config.processCompiler(compiler);
            Template tmpl = compiler
                    .withLoader(new Mustache.TemplateLoader() {
                        @Override
                        public Reader getTemplate(String name) {
                            return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
                        }
                    })
                    .defaultValue("")
                    .compile(template);

            writeToFile(adjustedOutputFilename, tmpl.execute(templateData));
            return new File(adjustedOutputFilename);
        }

        LOGGER.info("Skipped generation of " + adjustedOutputFilename + " due to rule in .swagger-codegen-ignore");
        return null;
    }

继续跟踪上述方法中的getFullTemplateFile()方法:

    /**
     * Get the template file path with template dir prepended, and use the
     * library template if exists.
     *
     * @param config Codegen config
     * @param templateFile Template file
     * @return String Full template file path
     */
    public String getFullTemplateFile(CodegenConfig config, String templateFile) {
        //1st the code will check if there's a <template folder>/libraries/<library> folder containing the file
        //2nd it will check for the file in the specified <template folder> folder
        //3rd it will check if there's an <embedded template>/libraries/<library> folder containing the file
        //4th and last it will assume the file is in <embedded template> folder.

        //check the supplied template library folder for the file
        final String library = config.getLibrary();
        if (StringUtils.isNotEmpty(library)) {
            //look for the file in the library subfolder of the supplied template
            final String libTemplateFile = buildLibraryFilePath(config.templateDir(), library, templateFile);
            if (new File(libTemplateFile).exists()) {
                return libTemplateFile;
            }
        }

        //check the supplied template main folder for the file
        final String template = config.templateDir() + File.separator + templateFile;
        if (new File(template).exists()) {
            return template;
        }

        //try the embedded template library folder next
        if (StringUtils.isNotEmpty(library)) {
            final String embeddedLibTemplateFile = buildLibraryFilePath(config.embeddedTemplateDir(), library, templateFile);
            if (embeddedTemplateExists(embeddedLibTemplateFile)) {
                // Fall back to the template file embedded/packaged in the JAR file library folder...
                return embeddedLibTemplateFile;
            }
        }
            
        // Fall back to the template file embedded/packaged in the JAR file...
        return config.embeddedTemplateDir() + File.separator + templateFile;
    }

上述方法里面,第一行就是获取config中的library属性,根据library去获取对应的模板,注意看该方法的注释也进行了描述,共有4种情况寻找模板,如果没有library属性,则会返回默认的模板。我们继续跟踪buildLibraryFilePath()方法:

    private String buildLibraryFilePath(String dir, String library, String file) {
        return dir + File.separator + "libraries" + File.separator + library + File.separator + file;
    }

上述方法就是获取libraries目录下的模板文件,我们现在去看下libraries目录下的文件长啥样:

swagger使用的是mustache作为模板引擎,关于mustache的介绍,本文不涉及。我们拿libraries目录下的JSON.mustache模板和外层的默认模板来对比。

library=jersey2的模板如下:

package {{invokerPackage}};

{{#threetenbp}}
import org.threeten.bp.*;
{{/threetenbp}}
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
{{#java8}}
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
{{/java8}}
{{#joda}}
import com.fasterxml.jackson.datatype.joda.JodaModule;
{{/joda}}
{{#threetenbp}}
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
{{/threetenbp}}

import java.text.DateFormat;

import javax.ws.rs.ext.ContextResolver;

{{>generatedAnnotation}}
public class JSON implements ContextResolver<ObjectMapper> {
  private ObjectMapper mapper;

  public JSON() {
    mapper = new ObjectMapper();
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
    mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
    mapper.setDateFormat(new RFC3339DateFormat());
    {{#java8}}
    mapper.registerModule(new JavaTimeModule());
    {{/java8}}
    {{#joda}}
    mapper.registerModule(new JodaModule());
    {{/joda}}
    {{#threetenbp}}
    ThreeTenModule module = new ThreeTenModule();
    module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT);
    module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME);
    module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME);
    mapper.registerModule(module);
    {{/threetenbp}}
  }

  /**
   * Set the date format for JSON (de)serialization with Date properties.
   * @param dateFormat Date format
   */
  public void setDateFormat(DateFormat dateFormat) {
    mapper.setDateFormat(dateFormat);
  }

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return mapper;
  }
}

 没有library属性的模板如下:

{{>licenseInfo}}

package {{invokerPackage}};

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.gson.JsonElement;
import io.gsonfire.GsonFireBuilder;
import io.gsonfire.TypeSelector;
{{#joda}}
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.ISODateTimeFormat;
{{/joda}}
{{#threetenbp}}
import org.threeten.bp.LocalDate;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;
{{/threetenbp}}

{{#models.0}}
import {{modelPackage}}.*;
{{/models.0}}
import okio.ByteString;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
{{#java8}}
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
{{/java8}}
import java.util.Date;
import java.util.Map;
import java.util.HashMap;

public class JSON {
    private Gson gson;
    private boolean isLenientOnJson = false;
    private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();
    private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter();
    {{#joda}}
    private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter();
    private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter();
    {{/joda}}
    {{#jsr310}}
    private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter();
    private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter();
    {{/jsr310}}
    private ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter();

    public static GsonBuilder createGson() {
        GsonFireBuilder fireBuilder = new GsonFireBuilder()
        {{#parent}}
          .registerTypeSelector({{classname}}.class, new TypeSelector() {
            @Override
            public Class getClassForElement(JsonElement readElement) {
                Map classByDiscriminatorValue = new HashMap();
                {{#children}}
                classByDiscriminatorValue.put("{{name}}".toUpperCase(), {{classname}}.class);
                {{/children}}
                classByDiscriminatorValue.put("{{classname}}".toUpperCase(), {{classname}}.class);
                return getClassByDiscriminator(
                                           classByDiscriminatorValue,
                                           getDiscriminatorValue(readElement, "{{discriminator}}"));
            }
          })
        {{/parent}}
        ;
        GsonBuilder builder = fireBuilder.createGsonBuilder();
        {{#disableHtmlEscaping}}
        builder.disableHtmlEscaping();
        {{/disableHtmlEscaping}}
        return builder;
    }

    private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) {
        JsonElement element = readElement.getAsJsonObject().get(discriminatorField);
        if(null == element) {
            throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">");
        }
        return element.getAsString();
    }

    private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) {
        Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue.toUpperCase());
        if(null == clazz) {
            throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">");
        }
        return clazz;
    }

    public JSON() {
        gson = createGson()
            .registerTypeAdapter(Date.class, dateTypeAdapter)
            .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
            {{#joda}}
            .registerTypeAdapter(DateTime.class, dateTimeTypeAdapter)
            .registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
            {{/joda}}
            {{#jsr310}}
            .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
            .registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
            {{/jsr310}}
            .registerTypeAdapter(byte[].class, byteArrayAdapter)
            .create();
    }

    /**
     * Get Gson.
     *
     * @return Gson
     */
    public Gson getGson() {
        return gson;
    }

    /**
     * Set Gson.
     *
     * @param gson Gson
     * @return JSON
     */
    public JSON setGson(Gson gson) {
        this.gson = gson;
        return this;
    }

    public JSON setLenientOnJson(boolean lenientOnJson) {
        isLenientOnJson = lenientOnJson;
        return this;
    }

    /**
     * Serialize the given Java object into JSON string.
     *
     * @param obj Object
     * @return String representation of the JSON
     */
    public String serialize(Object obj) {
        return gson.toJson(obj);
    }

    /**
     * Deserialize the given JSON string to Java object.
     *
     * @param <T>        Type
     * @param body       The JSON string
     * @param returnType The type to deserialize into
     * @return The deserialized Java object
     */
    @SuppressWarnings("unchecked")
    public <T> T deserialize(String body, Type returnType) {
        try {
            if (isLenientOnJson) {
                JsonReader jsonReader = new JsonReader(new StringReader(body));
                // see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean)
                jsonReader.setLenient(true);
                return gson.fromJson(jsonReader, returnType);
            } else {
                return gson.fromJson(body, returnType);
            }
        } catch (JsonParseException e) {
            // Fallback processing when failed to parse JSON form response body:
            // return the response body string directly for the String return type;
            if (returnType.equals(String.class))
                return (T) body;
            else throw (e);
        }
    }

    /**
     * Gson TypeAdapter for Byte Array type
     */
    public class ByteArrayAdapter extends TypeAdapter<byte[]> {

        @Override
        public void write(JsonWriter out, byte[] value) throws IOException {
            if (value == null) {
                out.nullValue();
            } else {
                out.value(ByteString.of(value).base64());
            }
        }

        @Override
        public byte[] read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String bytesAsBase64 = in.nextString();
                    ByteString byteString = ByteString.decodeBase64(bytesAsBase64);
                    return byteString.toByteArray();
            }
        }
    }

    {{#joda}}
    /**
     * Gson TypeAdapter for Joda DateTime type
     */
    public static class DateTimeTypeAdapter extends TypeAdapter<DateTime> {

        private DateTimeFormatter formatter;

        public DateTimeTypeAdapter() {
            this(new DateTimeFormatterBuilder()
                .append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser())
                .toFormatter());
        }

        public DateTimeTypeAdapter(DateTimeFormatter formatter) {
            this.formatter = formatter;
        }

        public void setFormat(DateTimeFormatter dateFormat) {
            this.formatter = dateFormat;
        }

        @Override
        public void write(JsonWriter out, DateTime date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                out.value(formatter.print(date));
            }
        }

        @Override
        public DateTime read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String date = in.nextString();
                    return formatter.parseDateTime(date);
            }
        }
    }

    /**
     * Gson TypeAdapter for Joda LocalDate type
     */
    public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {

        private DateTimeFormatter formatter;

        public LocalDateTypeAdapter() {
            this(ISODateTimeFormat.date());
        }

        public LocalDateTypeAdapter(DateTimeFormatter formatter) {
            this.formatter = formatter;
        }

        public void setFormat(DateTimeFormatter dateFormat) {
            this.formatter = dateFormat;
        }

        @Override
        public void write(JsonWriter out, LocalDate date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                out.value(formatter.print(date));
            }
        }

        @Override
        public LocalDate read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String date = in.nextString();
                    return formatter.parseLocalDate(date);
            }
        }
    }

    public JSON setDateTimeFormat(DateTimeFormatter dateFormat) {
        dateTimeTypeAdapter.setFormat(dateFormat);
        return this;
    }

    public JSON setLocalDateFormat(DateTimeFormatter dateFormat) {
        localDateTypeAdapter.setFormat(dateFormat);
        return this;
    }

    {{/joda}}
    {{#jsr310}}
    /**
     * Gson TypeAdapter for JSR310 OffsetDateTime type
     */
    public static class OffsetDateTimeTypeAdapter extends TypeAdapter<OffsetDateTime> {

        private DateTimeFormatter formatter;

        public OffsetDateTimeTypeAdapter() {
            this(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
        }

        public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) {
            this.formatter = formatter;
        }

        public void setFormat(DateTimeFormatter dateFormat) {
            this.formatter = dateFormat;
        }

        @Override
        public void write(JsonWriter out, OffsetDateTime date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                out.value(formatter.format(date));
            }
        }

        @Override
        public OffsetDateTime read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String date = in.nextString();
                    if (date.endsWith("+0000")) {
                        date = date.substring(0, date.length()-5) + "Z";
                    }
                    return OffsetDateTime.parse(date, formatter);
            }
        }
    }

    /**
     * Gson TypeAdapter for JSR310 LocalDate type
     */
    public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {

        private DateTimeFormatter formatter;

        public LocalDateTypeAdapter() {
            this(DateTimeFormatter.ISO_LOCAL_DATE);
        }

        public LocalDateTypeAdapter(DateTimeFormatter formatter) {
            this.formatter = formatter;
        }

        public void setFormat(DateTimeFormatter dateFormat) {
            this.formatter = dateFormat;
        }

        @Override
        public void write(JsonWriter out, LocalDate date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                out.value(formatter.format(date));
            }
        }

        @Override
        public LocalDate read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String date = in.nextString();
                    return LocalDate.parse(date, formatter);
            }
        }
    }

    public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) {
        offsetDateTimeTypeAdapter.setFormat(dateFormat);
        return this;
    }

    public JSON setLocalDateFormat(DateTimeFormatter dateFormat) {
        localDateTypeAdapter.setFormat(dateFormat);
        return this;
    }

    {{/jsr310}}
    /**
     * Gson TypeAdapter for java.sql.Date type
     * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used
     * (more efficient than SimpleDateFormat).
     */
    public static class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {

        private DateFormat dateFormat;

        public SqlDateTypeAdapter() {
        }

        public SqlDateTypeAdapter(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
        }

        public void setFormat(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
        }

        @Override
        public void write(JsonWriter out, java.sql.Date date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                String value;
                if (dateFormat != null) {
                    value = dateFormat.format(date);
                } else {
                    value = date.toString();
                }
                out.value(value);
            }
        }

        @Override
        public java.sql.Date read(JsonReader in) throws IOException {
            switch (in.peek()) {
                case NULL:
                    in.nextNull();
                    return null;
                default:
                    String date = in.nextString();
                    try {
                        if (dateFormat != null) {
                            return new java.sql.Date(dateFormat.parse(date).getTime());
                        }
                        return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime());
                    } catch (ParseException e) {
                        throw new JsonParseException(e);
                    }
            }
        }
    }

    /**
     * Gson TypeAdapter for java.util.Date type
     * If the dateFormat is null, ISO8601Utils will be used.
     */
    public static class DateTypeAdapter extends TypeAdapter<Date> {

        private DateFormat dateFormat;

        public DateTypeAdapter() {
        }

        public DateTypeAdapter(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
        }

        public void setFormat(DateFormat dateFormat) {
            this.dateFormat = dateFormat;
        }

        @Override
        public void write(JsonWriter out, Date date) throws IOException {
            if (date == null) {
                out.nullValue();
            } else {
                String value;
                if (dateFormat != null) {
                    value = dateFormat.format(date);
                } else {
                    value = ISO8601Utils.format(date, true);
                }
                out.value(value);
            }
        }

        @Override
        public Date read(JsonReader in) throws IOException {
            try {
                switch (in.peek()) {
                    case NULL:
                        in.nextNull();
                        return null;
                    default:
                        String date = in.nextString();
                        try {
                            if (dateFormat != null) {
                                return dateFormat.parse(date);
                            }
                            return ISO8601Utils.parse(date, new ParsePosition(0));
                        } catch (ParseException e) {
                            throw new JsonParseException(e);
                        }
                }
            } catch (IllegalArgumentException e) {
                throw new JsonParseException(e);
            }
        }
    }

    public JSON setDateFormat(DateFormat dateFormat) {
        dateTypeAdapter.setFormat(dateFormat);
        return this;
    }

    public JSON setSqlDateFormat(DateFormat dateFormat) {
        sqlDateTypeAdapter.setFormat(dateFormat);
        return this;
    }

}

由此可以看出,library=jersey2的时候,引入的第三方包很少,也不涉及开始报错的那个包,自此,问题得到答案,心中豁然开朗不少。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1、springcloud环境搭建

目录 1、创建一个父项目 ​编辑 2、创建子项目 2.1创建订单系统-order ​编辑 2.2创建库存系统-stock 3、创建rest服务 3.1添加web依赖 3.2编写controller 3.3订单中需要调用库存中的扣减库存的接口 通过idea开发工具进行搭建 1、创建一个父项目 通过spring initializr…

QT day4 (time/tcp/draw)

如图所示设计一个闹钟 1、头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QColor> #include <QDebug> #include <QMessageBox> #include <QTimer> //定时器类的头文件 #include <QTime> …

搞定剑桥面试数学题番外篇2:使用多线程并发“加强版”

0. 概览 我们在之前三篇博文中已经介绍了如何用多种语言&#xff08;ruby、swift、c、x64 汇编和 ARM64 汇编&#xff09;实现一道“超超超难”的剑桥数学面试题&#xff1a; 有趣的小实验&#xff1a;四种语言搞定“超超超难”剑桥面试数学题 搞定“超超超难”剑桥面试数学…

【每日挠头算法题(7)】对称的二叉树|二叉树的所有路径

欢迎&#xff01; 前言一、对称的二叉树思路&#xff1a;递归法具体代码如下&#xff1a; 二、二叉树的所有路径思路&#xff1a;递归法具体代码如下&#xff1a; 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不…

如何快速选择合适的会计软件?这些推荐值得尝试!

现今&#xff0c;许多公司都在使用会计软件来管理它们的财务&#xff0c;提高工作效率。因此选择一个适合自己公司的会计软件是相当重要的。但是&#xff0c;对于许多小型企业而言&#xff0c;如何选择最适合自己的会计软件并不容易。那么&#xff0c;该如何选择合适的会计软件…

开启跨平台之旅:学习Flutter,掌握移动应用开发的未来

Flutter是一种开源的移动应用开发框架&#xff0c;由Google开发和维护。它使用Dart语言进行编写&#xff0c;并提供了丰富的UI组件和工具&#xff0c;用于构建高性能、跨平台的移动应用程序。 优势 跨平台开发&#xff1a;Flutter是一种跨平台的移动应用开发框架&#xff0c;…

管理类联考——英语——技巧篇——时态表

一般现在时 1.概念&#xff1a;经常、反复发生的动作或行为及现在的某种状况。 2.基本结构&#xff1a;①is/am/are;②do/does否定形式&#xff1a;①am/is/are not;②此时态的谓语动词若为行为动词&#xff0c;则在其前加don‘t&#xff0c;如主语为第三人称单数&#xff0c…

资深测试总结,性能测试-业务量/吞吐量/存量数据设计关联(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 业务量 是不带时…

HTML | html文档中html和body标签的默认尺寸是多少?

新建一个空白的html文件&#xff0c;如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><title></title><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

day56|动态规划16-编辑距离问题

583. 两个字符串的删除操作 明确dp数组的含义&#xff1a; dp[i][j] 以i-1为结尾的word1和以j-1为结尾的word2&#xff0c;为相同的最小操作次数递归函数&#xff1a; if word1[i-1] word1[j-1]: dp[i][j] dp[i-1][j-1] # 不需要删除&#xff0c;只看上一层的字符串即可 else…

阿里云如何帮助企业进行数据迁移和数据同步?有哪些应用案例?

阿里云如何帮助企业进行数据迁移和数据同步&#xff1f;有哪些应用案例&#xff1f; [本文由阿里云代理商[聚搜云www.4526.cn]撰写] 阿里云数据迁移与数据同步解决方案 阿里云为企业提供了一系列高效、安全并应对不同场景需求的数据迁移与同步服务。这些服务旨在最大范围减少企…

Seata学习 @GlobalTransactional注解的作用

Seata学习 GlobalTransactional注解的作用 1.自动配置类 SeataAutoConfiguration 引入 seata与SpringBoot的整合包后&#xff0c;基于SpringBoot的自动配置&#xff0c;会往Spring容器中自动添加 SeataAutoConfiguration 而 SeataAutoConfiguration 配置类又会往容器中添加be…

基于Java汽车客运站管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

TrafficRoute:一体化的DNS解析和流量调度套件

TrafficRoute是火山引擎推出的解析调度套件&#xff0c;它实现了“一体化”的解析和调度服务&#xff0c;覆盖「公网解析」、「私网解析」、「流量调度/容灾」等场景&#xff0c;提供高性能、高可用、精准、及时、安全和可定制的解析调度产品集。 TrafficRoute具备云解析 DNS、…

华为OD机试真题 JavaScript 实现【字符串通配符】【2022Q4 200分】

一、题目描述 问题描述&#xff1a;在计算机中&#xff0c;通配符一种特殊语法&#xff0c;广泛应用于文件搜索、数据库、正则表达式等领域。现要求各位实现字符串通配符的算法。 要求&#xff1a; 实现如下2个通配符&#xff1a; &#xff1a;匹配0个或以上的字符&#xf…

【一文解决】已安装CUDA与Pytorch但torch.cuda.is_available()为False

目录 问题描述总览&#xff1a;导致问题的原因可能1&#xff1a;CUDA版本与驱动程序不兼容可能2&#xff1a;CUDA库的路径设置存在问题可能3&#xff1a;PyTorch版本与CUDA版本不匹配可能4&#xff1a;编译问题可能5&#xff1a;软件包或库冲突写在最后 问题描述 已经安装CUDA…

2023 陕西省大学生网络安全技能大赛 --- 高职组 Crypto ezmath

文章目录 题目解题过程 题目 from Crypto.Util.number import * from secret import y,a,b flagbflag{} l len(flag) m1, m2 flag[: l // 2], flag[l // 2:]x bytes_to_long(m1) c bytes_to_long(m2)assert (x**21)*(y**21)-2*(xy)*(x*y1)gift-4*x*y 4*b**6-2*a**33*a*c …

JavaScript案例分享:让前端开发者抓狂的按钮

前言 我分享一个前端案例&#xff0c;代码来源我不确定&#xff0c;可能是我某个编程技术交流群的群友分享的。由于我设置了自动下载文件&#xff0c;今天在查看微信文件时偶然发现了这个案例&#xff0c;查看下载日期是6月7日&#xff0c;所以无法确定到底是哪个群友分享的。但…

618,你会入手哪些书?【文末送书】

好书分享 前沿技术人工智能半导体新一代通信与信息技术网络空间安全参与规则 一年一度的618又到啦&#xff01;今年的618就不要乱买啦&#xff0c;衣服买多了会被淘汰&#xff0c;电子产品买多了会过时&#xff0c;零食买多了会增肥&#xff0c;最后怎么看都不划算。可是如果你…

超强整理,性能测试-常用服务器性能指标分析总结,一篇概全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 压测过程中&#…