使用SpringDoc时,FastJson作为HttpMessageConverters的问题

使用SpringDoc时,FastJson作为HttpMessageConverters的问题

从notion直接导出,可能存在排版问题, Notion在线文档链接:yuxs.notion.site/SpringDoc-F…

Tags: I

[BUG] 集成openapi有误,参考#387 · Issue #1256 · alibaba/fastjson2

上面链接是在FastJson2中有人给出的详细解答。

我的项目中使用的是FastJson1的最新版本,同样也存在这个问题。

问题

项目同时使用了FastJsonHttpMessageConverter 作为HTTP消息转换器,并且配置了springdoc-openapi-ui 之后,进入SpringDoc的swagger-ui/index.html 报错:

Unable to render this definition
The provided definition does not specify a valid version field.

Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: “2.0” and those that match openapi: 3.0.n (for example, openapi: 3.0.0).

项目背景

项目中添加了springdoc-openapi-ui依赖,用来自动生成项目的接口文档;项目本身添加了FastJsonHttpMessageConverter 作为*HttpMessageConverter(SpringMVC处理HTTP消息转换器的集合)*

采用配置了HttpMessageConverters 的类Bean 进行注入。所以就提供了一个将FastJsonHttpMessageConverter 作为消息转换器列表的第一个元素的集合。

Untitled.png

Debug

? 需要注意的是,在对`org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)`调试过程中,进入swagger-ui页面时,会发起两次请求,第一个请求,返回的数据之前,类型是一个`TreeMap`,我们不需要关心,它会被正常接受。第二个请求在返回之前是一个`byte[]` ,此时fastjson处理之后不能被正常接受使用。

在debug后发现OpenApi的方法org.springdoc.api.AbstractOpenApiResource#writeJsonValue 是采用JackSon将所有接口的配置信息转化成了一个byte[]数组,并返回:

Untitled 1.png

接着对org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters方法调试,该方法的核心作用就是按照要返回的结果类型,匹配一个合适的*HttpMessageConverter* 通过消息转换器的处理,返回数据。

核心代码部分:

Untitled 2.png

在进入swager-ui页面时,对api-doc的请求头中,accept标识了为application/json,**/** 表明客户端优先想接受到json数据格式。恰好此时我们服务端的消息转换处理器集合*this*.messageConverters的第一个元素就是可以处理该返回类型的(FastJson的消息处理转换器)

Untitled 3.png

跟具体一点,会在FastJson详细转换器的canWrite方法中返回true,从而使用FastJson作为转换器:

Untitled 4.png

Untitled 5.png

并且还发现一个东西,FastJson消息转换器默认会接受所有媒体类型的消息处理-_-。所以将FastJson的消息转换器放在第一位,处理完之后,直接return,其它消息处理也不会再遍历。绝杀。。。。

Untitled 6.png

接着再看FastJsonHttpMessageConverter内部的关键方法是怎么处理byte[]数组转换的:

Untitled 7.png

Untitled 8.png

前面的判断都不会命中,因为value是byte[]类型的。那么这个JSON.*writeJSONStringWithFastJsonConfig* 是如何处理输出值的?

Untitled 9.png

看到这,需要补充点说明:

? 在 Fastjson 中,`SerializeWriter` 和 `JSONSerializer` 是两个核心类,用于序列化 Java 对象为 JSON 字符串。

  1. SerializeWriterSerializeWriter 是一个底层的字符缓冲区,用于将 Java 对象序列化为 JSON 字符串。它提供了一系列方法来操作和构建字符串缓冲区,用于存储序列化后的 JSON 数据。在序列化过程中,SerializeWriter 将会被 JSONSerializer 使用来写入最终的 JSON 字符串。
  2. JSONSerializerJSONSerializer 是负责将 Java 对象序列化为 JSON 字符串的类。它会遍历 Java 对象的属性,并在 SerializeWriter 中生成相应的 JSON 数据。JSONSerializer 同时也支持配置不同的序列化选项,例如设置日期格式、处理循环引用等。

下面给一个简短的代码示例,辅助对照理解:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Main {
    public static void main(String[] args) {
        MyObject obj = new MyObject();
        obj.setId(1);
        obj.setName("John");

        // 创建 SerializeWriter 对象
        SerializeWriter writer = new SerializeWriter();

        // 创建 JSONSerializer 对象,并设置 SerializeWriter
        JSONSerializer serializer = new JSONSerializer(writer);
        // 配置序列化选项,例如设置日期格式、处理循环引用等
        serializer.config(SerializerFeature.WriteDateUseDateFormat, true);

        // 将 Java 对象序列化为 JSON 字符串
        serializer.write(obj);
        String jsonString = writer.toString();

        System.out.println(jsonString);

        // 关闭 SerializeWriter
        writer.close();
    }
}

class MyObject {
    private int id;
    private String name;
    // 省略 getter 和 setter 方法
}

在上面的示例中,我们创建了一个 SerializeWriter 对象来存储序列化后的 JSON 数据。然后,我们创建了一个 JSONSerializer 对象,并将 SerializeWriter 对象传递给它。通过调用 serializer.write(obj) 来将 Java 对象序列化为 JSON 字符串。最后,我们通过调用 writer.toString() 获取最终的 JSON 字符串。

总结:SerializeWriter 是 Fastjson 中底层的字符缓冲区,用于存储序列化后的 JSON 数据;JSONSerializer 是负责处理 Java 对象序列化为 JSON 字符串的类,它使用 SerializeWriter 对象来写入最终的 JSON 数据。

进入write方法内部:

Untitled 10.png

这里获得了一个ObjectSerializer ,实际类型是PrimitiveArraySerializer

? 在 Fastjson 中,`ObjectSerializer` 是一个接口,用于自定义对象序列化的行为。通过实现 `ObjectSerializer` 接口,你可以定义自己的序列化规则,以满足特定的需求。

ObjectSerializer 接口定义了一个方法 void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features),该方法接收以下参数:

  • JSONSerializer serializer:当前的 JSON 序列化器,你可以通过它来实现更复杂的序列化逻辑。
  • Object object:待序列化的对象。
  • Object fieldName:待序列化的对象的属性名。
  • Type fieldType:待序列化的对象的属性类型,可以帮助你实现更加精确的序列化。
  • int features:序列化特性,例如日期格式化、循环引用处理等。

自定义的序列化器需要实现 ObjectSerializer 接口,并根据自己的需求实现 write 方法。在 write 方法中,你可以根据待序列化对象的类型、属性名和属性类型,编写逻辑来生成相应的 JSON 数据。

这个PrimitiveArraySerializer 就是按照数组类型匹配,然后按匹配到的类型写到SerializeWriter 中,其中最后匹配到了byte[]:

Untitled 11.png

writeByteArray(*byte*[] bytes)方法内部,按照base64算法将byte[]数组编码输出,有兴趣可以看一下。

Untitled 12.png

到这,就明白了最后接口返回的数据是一个被base64编码后的纯文本内容,并不是结构化的json数组。

处理方法也很简单,只要让byte[] 的消息处理走默认的*ByteArrayHttpMessageConverter* 就行。在SpringBoot默认的配置中,ByteArrayHttpMessageConverter始终是在消息列表处理的第一个,也就是0号元素,所以只需要这样处理即可:

@Component
public class WebConfig implements WebMvcConfigurer {
		@Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        WebMvcConfigurer.super.configureMessageConverters(converters);
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // converters.add(0, new ByteArrayHttpMessageConverter());
        // 默认第0个转换器是ByteArrayHttpMessageConverter,处理byte[]数据的转换
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(1, fastJsonHttpMessageConverter);
    }
}

将Fastjson的消息处理筛在1号位置(第二个)。

这里还有个细节,扩展MessageConverters的逻辑建议写在extendMessageConverters方法中,而不是configureMessageConverters 至于为什么,可以看看Spring的源码注释。

到此,暂时Over!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYiSTkFQ' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片