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

news/2025/2/21 8:35:10/

前言:

        我们一般都会使用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 symbolsymbol: 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;}@Overridepublic MediaType contentType() {return requestBody.contentType();}@Overridepublic long contentLength() throws IOException {return requestBody.contentLength();}@Overridepublic 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;@Overridepublic 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()方法,我们就从该方法进入,以下是源码,不相关的代码都被删掉了:

@Overridepublic void execute() throws MojoExecutionException {// Using the naive approach for achieving thread safetysynchronized (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 fileCodegenConfigurator configurator = CodegenConfigurator.fromFile(configurationFile);// if a config file wasn't specified or we were unable to read itif (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 fileCodegenConfigurator configurator = CodegenConfigurator.fromFile(configurationFile);// if a config file wasn't specified or we were unable to read itif (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 modelsInlineModelResolver inlineModelResolver = new InlineModelResolver();inlineModelResolver.flatten(swagger);List<File> files = new ArrayList<File>();// modelsList<Object> allModels = new ArrayList<Object>();generateModels(files, allModels);// apisList<Object> allOperations = new ArrayList<Object>();generateApis(files, allOperations, allModels);// supporting filesMap<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>() {@Overridepublic 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 filesfor (String templateName : config.apiTestTemplateFiles().keySet()) {String filename = config.apiTestFilename(templateName, tag);// do not overwrite test file that already existsif (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 filesfor (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() {@Overridepublic 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 filefinal String library = config.getLibrary();if (StringUtils.isNotEmpty(library)) {//look for the file in the library subfolder of the supplied templatefinal String libTemplateFile = buildLibraryFilePath(config.templateDir(), library, templateFile);if (new File(libTemplateFile).exists()) {return libTemplateFile;}}//check the supplied template main folder for the filefinal String template = config.templateDir() + File.separator + templateFile;if (new File(template).exists()) {return template;}//try the embedded template library folder nextif (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);}@Overridepublic 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() {@Overridepublic 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[]> {@Overridepublic void write(JsonWriter out, byte[] value) throws IOException {if (value == null) {out.nullValue();} else {out.value(ByteString.of(value).base64());}}@Overridepublic 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;}@Overridepublic void write(JsonWriter out, DateTime date) throws IOException {if (date == null) {out.nullValue();} else {out.value(formatter.print(date));}}@Overridepublic 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;}@Overridepublic void write(JsonWriter out, LocalDate date) throws IOException {if (date == null) {out.nullValue();} else {out.value(formatter.print(date));}}@Overridepublic 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;}@Overridepublic void write(JsonWriter out, OffsetDateTime date) throws IOException {if (date == null) {out.nullValue();} else {out.value(formatter.format(date));}}@Overridepublic 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;}@Overridepublic void write(JsonWriter out, LocalDate date) throws IOException {if (date == null) {out.nullValue();} else {out.value(formatter.format(date));}}@Overridepublic 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;}@Overridepublic 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);}}@Overridepublic 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;}@Overridepublic 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);}}@Overridepublic 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.ppmy.cn/news/405838.html

相关文章

按键中断封装

RCC和GPIO初始化函数&#xff1a; EXTI初始化函数&#xff1a;通用的函数&#xff0c;对KEY1/KEY2/KEY3进行使用 void hal_exti_init(事件编号&#xff0c;GPIOF组对应编号&#xff0c;触发方式) GIC层初始化函数&#xff1a;通用的函数&#xff0c;对KEY1/KEY2/KEY3进行使用…

Linux(CentOS 7)下安装配置Maven3.9.2

Linux&#xff08;CentOS 7&#xff09;下安装配置Maven3.9.2 环境 JDK 1.8OS:Centos 7.5 tar包安装 下载 apache-maven-3.9.2-bin.tar.gz https://maven.apache.org/download.cgi 安装步骤 公共服务&#xff0c;将maven安装在root用户下。 创建maven安装地址解压安装…

大面积无线WIFI覆盖 H3C WX3010E(AC+PoE三层交换机)+ H3C WA2620E、WA4320无线AP +华为USG6310S防火墙

一、适用场景&#xff1a; 1、跨复杂区域覆盖WIFI。支持多房间、多栋、多层复式楼、别墅、自建房的无线WIFI覆盖。 2、强大的漫游功能。楼上楼下移动使用WIFI时&#xff0c;需要支持WIFI的信号漫游&#xff0c;更换地理位置不掉线、不中断。 3、用户量或网络流量的负载均衡功…

蓝牙ble的常见概念

蓝牙广播 包组成结构 低功耗蓝牙一共有40个信道&#xff0c;频段范围从2402Mhz-2480Mhz&#xff0c;每2Mhz一个信道&#xff0c;37 38 39 是广播信道&#xff0c;其余为数据信道 一个广播信道最长37字节&#xff0c;有6字节用作蓝牙设备的MAC地址&#xff0c;我们只需要关注剩…

精读笔记 - Attack of the Tails: Yes, You Really Can Backdoor Federated Learning

文章目录 精读笔记 - Attack of the Tails: Yes, You Really Can Backdoor Federated Learning1. 基本信息2. 核心贡献 与 基本原理2.1 edge-case 形式化定义2.2 edge-case backdoor基本模块2.2.1 黑盒攻击2.2.2 PGD攻击2.3 算法基本步骤3. 实验验证 与 结果分析3.1 实验设置(…

Nucleo-F411RE (STM32F411)LL库体验 3 - 滴嗒定时器的配置

Nucleo-F411RE &#xff08;STM32F411&#xff09;LL库体验 3 - 滴嗒定时器的配置 1、LL库延时 LL库初始化时钟的时候调用了LL_Init1msTick(100000000)函数&#xff0c;这个函数其实就是初始化了系统的滴答定时器。 LL_InitTick原型如下&#xff1a; load值 sysclk/1000&a…

字符设备驱动内部实现

只要文件存在&#xff0c;就会有唯一对应的inode号&#xff0c;且相应的会存在一个struct inode结构体.,在应用层通过open&#xff08;&#xff09;打开一个设备文件&#xff0c;会对应产生一个inode号&#xff0c;通过inode号可以找到文件的inode结构体&#xff0c;inode结构体…

【linux网络配置】多个网卡一起使用,一个网卡连内网,一个网卡连外网

一、问题背景 因为有一个工作站在内网中&#xff0c;但是没有办法联网&#xff08;校园网账户有限&#xff09;。 虽然工作站没有联网&#xff0c;但是我仍然可以通过局域网远程控制工作站&#xff0c;使其访问校园网验证页面实现上网。 当给工作站安装软件或依赖项时&#…