一起读源码 —— Fastjson 的核心方法及其实现原理

源码介绍

Fastjson 是阿里巴巴开源的一个 Java 工具库,它常常被用来完成 Java 的对象与 JSON 格式的字符串的相互转化。

此文读的源码是撰写此文时 Fastjson 的最新的发布版本,即 1.2.83

下载源码

请前去 github 找到 release 最新版下载后解压,地址为:https://github.com/alibaba/fastjson/releases/tag/1.2.83

项目结构

使用 IDEA 打开 fastjson-1.2.83 文件夹,下载相关依赖后,我们再开始阅读源码,接下来我们分别对 JSONJSONArrayJSONObject

在这里插入图片描述

JSON 实现两个接口

JSON 类是实现两个接口 JSONStreamAwareJSONAware 的抽象类,即
public abstract class JSON implements JSONStreamAware, JSONAware

我们先介绍这两个接口:

JSONStreamAware

方法 writeJSONString 中的参数 Appendable 也是一个接口,也就是可以附加字符序列和值的对象,这个接口的作用是提供一个输出JSON字符串的方法,以便于其他的方法调用。
在这里插入图片描述在这里插入图片描述
接着我们在 JSON 抽象类中寻找用到这个方法的地方,这里主要截图其中两个具体的实现方法,另外还有几个重载方法是通过调用这两个方法的方法,这里不再列举。
在这里插入图片描述
在这里插入图片描述

JSONAware

这个接口更加简单了,实现该接口的类即需要实现 toJSONString 的方法即可。
在这里插入图片描述
在JSON抽象类中,具体实现为:
在这里插入图片描述
这里调用的是 JSONSerializer 类的对象方法,这里我们在后面介绍这个类的时候进一步介绍。

这个相对于 writeJSONString 更加实用。比如重写 toString 方法。
在这里插入图片描述

JSON 类的静态方法

我们常常用到这些方法,并且我们在使用 JSONObject 类方法的时候也经常用得到(JSONObject是JSON类的子类)。这里我们介绍我们最最最常用的,并且在 JSONObject 中不再介绍。

JSON.toString / JSON.toJSONString

这个方法前面关于 JSONAware 接口的实现的时候有提到过,这里就不做介绍了。

这里我们看一下 toJSONString 方法的实现原理。源码比较简单,这里就不逐行介绍了。

方法参数介绍

  • object: Object 待转换的 Java 对象;
  • defaultFeatures: int 后面参数 SerializerFeature 的长度,因为后面是 SerializerFeature... 类型的,所以调用时需要指定它的长度;
  • features: SerializerFeature… 这个地方是指多个 SerializerFeature 类型的对象,调用时可以 toJSONString(obj, 1, feature0) 也可以 toJSONString(obj, 2, feature0, feature1),也可以 toJSONString(obj, 3, feature0, feature1, feature2) 等等。
    /**
     * @since 1.2.11
     */
    public static String toJSONString(Object object, int defaultFeatures, SerializerFeature... features) {
        SerializeWriter out = new SerializeWriter((Writer) null, defaultFeatures, features);

        try {
            JSONSerializer serializer = new JSONSerializer(out);
            serializer.write(object);
            String outString = out.toString();
            int len = outString.length();
            if (len > 0
                    && outString.charAt(len -1) == '.'
                    && object instanceof Number
                    && !out.isEnabled(SerializerFeature.WriteClassName)) {
                return outString.substring(0, len - 1);
            }
            return outString;
        } finally {
            out.close();
        }
    }

源码中用到了 SerializeWriter 对象以及它的方法,这里我们理一下它的作用与用法。

SerializeWriter 构造函数为

    public SerializeWriter(Writer writer, int defaultFeatures, SerializerFeature... features){
        this.writer = writer;

        buf = bufLocal.get();

        if (buf != null) {
            bufLocal.set(null);
        } else {
            buf = new char[2048];
        }

        int featuresValue = defaultFeatures;
        for (SerializerFeature feature : features) {
            featuresValue |= feature.getMask();
        }
        this.features = featuresValue;

        computeFeatures();
    }

在前面初始化JSONSerializer的时候调用SerializeWriter对象,即 JSONSerializer serializer = new JSONSerializer(out); 这个时候的使用方法是:

这里查看 JSONSerializer 对象的构造函数:

    public JSONSerializer(SerializeWriter out){
        this(out, SerializeConfig.getGlobalInstance());
    }
    public JSONSerializer(SerializeWriter out, SerializeConfig config){
        this.out = out;
        this.config = config;
    }

其 write 方法源码为:

    public final void write(Object object) {
        if (object == null) {
            out.writeNull();
            return;
        }

        Class<?> clazz = object.getClass();
        ObjectSerializer writer = getObjectWriter(clazz);

        try {
            writer.write(this, object, null, null, 0);
        } catch (IOException e) {
            throw new JSONException(e.getMessage(), e);
        }
    }

JSONObject

相对而言 JSONObject 用得更多,这里需要介绍的也更多。

无参构造函数

其中 DEFAULT_INITIAL_CAPACITY 默认等于 16,也就是创建 HashMap 或者 LinkedHashMap 对象的时候默认的 initialCapacity 的值,而 order 参数将会指定创建的 map 对象的类型。

    public JSONObject(){
        this(DEFAULT_INITIAL_CAPACITY, false);
    }
   	public JSONObject(int initialCapacity, boolean ordered){
        if (ordered) {
            map = new LinkedHashMap<String, Object>(initialCapacity);
        } else {
            map = new HashMap<String, Object>(initialCapacity);
        }
    }
    public JSONObject(boolean ordered){
        this(DEFAULT_INITIAL_CAPACITY, ordered);
    }

    public JSONObject(int initialCapacity){
        this(initialCapacity, false);
    }

有参构函数

    public JSONObject(Map<String, Object> map){
        if (map == null) {
            throw new IllegalArgumentException("map is null.");
        }
        this.map = map;
    }

这里相对于前面的无参构造函数而言,map 对象在外界创建与初始化,直接传入 JSONObject 中,作为构造函数。这样做使得我们在反序列化、序列化的方法更加灵活,这里我们在后面介绍。

containsKey / containsValue / get / isEmpty / size 方法

这几个方法都是直接调用 成员变量 map 得以实现的,具体实现代码如下:

	public int size() {
        return map.size();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public boolean containsKey(Object key) {
        boolean result = map.containsKey(key);
        if (!result) {
            if (key instanceof Number
                    || key instanceof Character
                    || key instanceof Boolean
                    || key instanceof UUID
            ) {
                result = map.containsKey(key.toString());
            }
        }
        return result;
    }

    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    public Object get(Object key) {
        Object val = map.get(key);

        if (val == null) {
            if (key instanceof Number
                    || key instanceof Character
                    || key instanceof Boolean
                    || key instanceof UUID
            ) {
                val = map.get(key.toString());
            }
        }

        return val;
    }

    public Object getOrDefault(Object key, Object defaultValue) {
        Object v;
        return ((v = get(key)) != null) ? v : defaultValue;
    }

反序列化方法

首先出场的是最简单的,将 map 中的某个 key 进行反序列化,一般情况下我们会在 这个 key 对应的是 Object 的时候使用它。比如原始的map 是 {"age": 3, "item" : {"color": "black", "length" : 2}} ,我们在反序列化 item 的时候需要调用这个方法,即 getJSONObject("item")

    public JSONObject getJSONObject(String key) {
        Object value = map.get(key);

        if (value instanceof JSONObject) {
            return (JSONObject) value;
        }

        if (value instanceof Map) {
            return new JSONObject((Map) value);
        }

        if (value instanceof String) {
            return JSON.parseObject((String) value);
        }

        return (JSONObject) toJSON(value);
    }

类似地如果是 JSON 数组的化,调用 getJSONArray 方法,这里举一个例子为 map{"age": 3, items:[{"color": "red"}, {"color": "black"}]},我们在反序列 items 的时候会调用这个方法。

    public JSONArray getJSONArray(String key) {
        Object value = map.get(key);

        if (value instanceof JSONArray) {
            return (JSONArray) value;
        }

        if (value instanceof List) {
            return new JSONArray((List) value);
        }

        if (value instanceof String) {
            return (JSONArray) JSON.parse((String) value);
        }

        return (JSONArray) toJSON(value);
    }

如果不是前面两种,我们需要获取的只是简单的 item 对象,比如 age = 3,那么就调用 getObject 方法即可,注意这里有几个重载方法。

    public <T> T getObject(String key, Class<T> clazz) {
        Object obj = map.get(key);
        return TypeUtils.castToJavaBean(obj, clazz);
    }

    public <T> T getObject(String key, Type type) {
        Object obj = map.get(key);
        return TypeUtils.cast(obj, type, ParserConfig.getGlobalInstance());
    }

    public <T> T getObject(String key, TypeReference typeReference) {
        Object obj = map.get(key);
        if (typeReference == null) {
            return (T) obj;
        }
        return TypeUtils.cast(obj, typeReference.getType(), ParserConfig.getGlobalInstance());
    }

这个时候我们不得不介绍一下 TypeUtils 类了,毕竟出场率这么高,这里只介绍 castcastToJavaBean 两个静态方法。

    private static BiFunction<Object, Class, Object> castFunction = new BiFunction<Object, Class, Object>() {
        public Object apply(Object obj, Class clazz) {
            if (clazz == java.sql.Date.class) {
                return castToSqlDate(obj);
            }

            if (clazz == java.sql.Time.class) {
                return castToSqlTime(obj);
            }

            if (clazz == java.sql.Timestamp.class) {
                return castToTimestamp(obj);
            }
            return null;
        }
    };

这里实现了接口 BiFunction 的 apply 方法,实现方法也非常简单粗暴,三个 if 对应三个方法 castToSql / castToSqlTime / castToTimestamp 。

这个时候可能大家会疑惑,这个跟 sql 有什么关系?其实确实没什么关系,但是毕竟 java 提供的现有的可用方法,不用白不用,这里也给大家做个广告,这里面的 java.sql.Timestampjava.sql.Time 以及 java.sql.Date 确实很好用,这里以 java.sql.Timestamp 为例,它提供13位时间戳的构造方法 public Timestamp(long time) 以及常用的 compareTobeforeafter 方法,需要的小伙伴可以自取使用。

cast 方法 居然都有 @SuppressWarnings("unchecked") 标注,着实让人多少有点不放心,难道一定要去使用 Fastjson2 ?

这里我们查看其中一个方法的实现,看完了你大概就会产生一种 “就这?我上我也行” 的感觉:

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj, Type type, ParserConfig mapping) {
        if (obj == null) {
            return null;
        }
        if (type instanceof Class) {
            return cast(obj, (Class<T>) type, mapping);
        }
        if (type instanceof ParameterizedType) {
            return (T) cast(obj, (ParameterizedType) type, mapping);
        }
        if (obj instanceof String) {
            String strVal = (String) obj;
            if (strVal.length() == 0 //
                    || "null".equals(strVal) //
                    || "NULL".equals(strVal)) {
                return null;
            }
        }
        if (type instanceof TypeVariable) {
            return (T) obj;
        }
        throw new JSONException("can not cast to : " + type);
    }

事实也确实如此,充其量就是用了点反射技术,首先判断数据类型属于哪个小可爱的子类,然后再使用放射和传过来的 clazz 创建对象,而建立其中的映射关系的,就是其中的 mapping 参数对象,仅此而已。

JSON 与 JSONObject 的区别与联系

能够用 JSON 的地方,基本上都能用 JSONObject,不同之处在于一般直接 JSON 的静态方法,因为它是抽象类,不能直接 new 出对象的。而 JSONObject 名字中就强调了 Object 的概念,所以一般直接就用它的对象的方法。

灵活使用 JSONObject

总体来说最经常用到的就是 toString 与 toJSONObject 以及 JSONObject 对象的 getInteger / getLong / getJSONObject 等等方法,这些方法都应该基于 map 联想记忆,都是比较容易理解的。

总结

Fastjson 中存在很多地方都属于让人看了就 “恍然大悟” 之处,也推荐大家去阅读,同时不得不说,阿里巴巴能够有支团队开发并不断完善Fastjson,也是挺值得前去 github 点颗星星。

最后吐槽一下 Fastjson 的实现部分有明显的 “不遵守 《JAVA 规范手册》” 的,请以后的开发人员注意多多参考 Java开发手册(嵩山版) ,多写一些些注释,成为一本与 手册 配套的代码示例,也是非常不错的 ~ 此致,敬上 ~

Smileyan
2023.04.13 00:44

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

贾俊平《统计学》第七章知识点总结及课后习题答案

一.考点归纳 参数估计的基本原理 1置信区间 &#xff08;1&#xff09;置信水平为95%的置信区间的含义&#xff1a;用某种方法构造的所有区间中有95%的区间包含总体参数的真值。&#xff08;2&#xff09;置信度愈高&#xff08;即估计的可靠性愈高&#xff09;&#xff0c;则…

【PMP】项目各阶段分别运用什么工具与技术汇总

做项目管理是一条漫漫长路&#xff0c;所有的本事&#xff0c;都是靠一个个项目&#xff0c;一点点积累而来的&#xff0c;并不存在“迅速上手”的方法论。一名普通项目经理的成长&#xff0c;都要经过一定时间的修炼。 数据收集 头脑风暴 在短时间内获得大量创意&#xff0…

SSM整合————单表操作基础版

一、创建数据库&#xff1a; 1. 创建一个 web 项目&#xff0c;并部署到 tomcat 服务器中测试项目 是否能够正常加载并访问首页。 2. 完善项目的结构并导入 SSM 相关的jar包 3.创建SSM框架对应的配置文件 springMVC配置文件&#xff1a;1.扫描controller&#xff1b;2.配置视图…

车企外卷:一个关于智能手机的“围城故事”

从2016年达到顶峰开始&#xff0c;全球智能手机出货量逐年下行&#xff0c;手机市场进入红海竞争逐渐成为了各界的共识。此后全球疫情与经济疲软的影响也进一步在手机市场施压&#xff0c;很多媒体认为手机产业距离“至暗时刻”已经不远。 而在去年&#xff0c;新增变数&#x…

奇葩营销之看各品牌如何玩转“营销疯学”

相信有很多人和我一样&#xff0c;最近的快乐都来自于《黑暗荣耀2》。 令人奇怪的是&#xff0c;但比起故事的主线&#xff0c;剧中妍珍等配角的”发疯”片段却成为了网友造梗的来源。 “妍珍疯驴子”“妍珍呐””“黑暗荣耀演我每天精神状态”等。让这部剧话题热度持续…

主题切换实现(vue-less)

介绍 本文适合黑白切换或者主题样式偏少的&#xff08;建议&#xff1a;2-10种&#xff09;&#xff1b;主题越多&#xff0c;样式会越多。理论上无限套。本文适合已经写好了一套主题&#xff0c;然后需求增加第二套或者多套主题&#xff08;最好小于10套&#xff0c;当然也可…

8、ThingsBoard使用docker compose集群部署的问题以及如何解决问题

1、问题回顾 接着上一节继续讲解,上一节我们把整个服务全部都运行起来了,但是访问页面报错,最后查看的问题是前端的容易里面报错: 然后执行脚本删除所有的容器 2、问题分析 当遇到这个问题的时候,我当时真的不知道如何去解决,然后我又尝试使用官方的镜像来部署,发现官…

ASM字节码处理工具原理及实践(一)

1. ASM简介 我们知道程序的分析。生成和转换是很有用的技术&#xff0c;可以用于很多场景。ASM作为一个Java字节码处理工具&#xff0c;它被设计用于处理已编译的Java类。ASM不是生成和转变已编译的Java类的唯一工具&#xff0c;但它是最新且最有效的工具之一。特点是体积小&a…

wsl的图像化实现,在wsl中启动浏览器

最近在学习wsl&#xff0c;原本我看以前的教程说wsl和vmware的区别有一点就是&#xff0c;wsl只能使用命令行&#xff0c;而vmware可以实现图像化&#xff0c;结果我在 microsoft 官方发现现在的wsl 2已经实现了 GUI 界面&#xff0c;所以就来记录一下吧。 wsl 的 GUI 实现 首…

web后端-请求响应

概述 我们之前在Spring写的 Java类&#xff0c;因为没有继承任何的接口 所以tomcat其实是不识别的&#xff0c;也不能直接运行 但是tomcat识别JavaEE的一项规范-Servlet,因为tomcat就相当于一个Servlet容器 SpringBoot底层提供了一个DisPatcherServlet类(实现了servlet接口)…

【linux】 安装 java 环境

目录 1.检查linux 下是否安装java(jdk)环境2.查看 linux 的操作系统版本3.下载jdk4.新建java文件夹用于安装jdk5.将下载到本地的jdk压缩包上传到linux服务器6.配置环境变量 1.检查linux 下是否安装java(jdk)环境 可通过下面五条命令来查看linux 系统是否安装了java 环境 1、jav…

建筑行业为什么要数字化转型?

建筑行业为什么要数字化转型&#xff1f; 建筑行业作为国民经济的重要支柱产业之一&#xff0c;其生产过程繁琐、复杂&#xff0c;且产业链条长&#xff0c;涉及众多领域。 然而&#xff0c;目前传统的建筑模式已经无法适应行业的发展需求&#xff0c;建筑行业的数字化转型已…

java SimpleDateFormat和Calendar日期类

目录 一、SimpleDateFormat使用二、Calendar使用 一、SimpleDateFormat使用 使用Date直接输出日期时&#xff0c;是使用系统默认的格式输出&#xff0c;所以需要使用SimpleDateFormat来格式化日期。 那么SimpleDateFormat类怎么使用呢&#xff0c;我们需要先了解此类的格式化符…

8.Java面向对象---类、属性和方法

Java面向对象—类、属性和方法 在生活中&#xff0c;说到类&#xff0c;可以联想到类别&#xff0c;同类&#xff0c;会想到一类人&#xff0c;一类事物等等。而这一类人或事物都是具有相同特征或特点和行为的&#xff0c;我们根据不同的特征或特点和行为将他们归类或分类。同…

Java多线程

一&#xff1a;进程与线程 概述&#xff1a;几乎任何的操作系统都支持运行多个任务&#xff0c;通常一个任务就是一个程序&#xff0c;而一个程序就是一个进程。当一个进程运行时&#xff0c;内部可能包括多个顺序执行流&#xff0c;每个顺序执行流就是一个线程。 进程&#x…

Faster-RCNN代码解读6:主要文件解读-中

Faster-RCNN代码解读6&#xff1a;主要文件解读-中 前言 ​ 因为最近打算尝试一下Faster-RCNN的复现&#xff0c;不要多想&#xff0c;我还没有厉害到可以一个人复现所有代码。所以&#xff0c;是参考别人的代码&#xff0c;进行自己的解读。 ​ 代码来自于B站的UP主&#xff…

【Linux系统】理解Linux中进程间通信

Linux进程间通信 1 进程间通信的介绍1.1为什么要有进程间通信1.2 为什么能进程间通信 2 进程间通信的框架2.1 进程间通信机制的结构2.2 进程间通信机制的类型2.2.1 共享内存式2.2.2 消息传递式 2.3 进程间通信的接口设计 3 进程间通信机制简介4 详细讲解进程间通信部分机制&…

文献管理软件Endnote、Mendeley、Zotero比较及选择,Zotero基础使用技巧

引言 大家好&#xff0c;我是比特桃。日常开发的项目分为两种&#xff0c;一种是成熟化的工程项目&#xff0c;只需要与具体的业务紧密结合及应用&#xff0c;难点也比较偏向于软件工程或者互联网高并发的方向。这种项目我们通常不会选择去查文献去寻找问题的解决办法&#xf…

Altium Designer 20 导出 Gerber 制造文件流程及注意事项

一、导出 Gerber 流程 设置原点&#xff1a;在Edit菜单中选择Origin&#xff0d;Set &#xff08;快捷键E-O-S&#xff09;定好原点&#xff0c;一般放在左下角附近即可。 放置分孔图表&#xff1a;在Place菜单中选择String放置“.Legend”&#xff08;快捷键P-S&#xff09;…

【java】maven引用外部 jar 包,以RXTXcomm.jar为例

目录 1.作为资源文件夹内的资源引用2.将jar包手动安装到maven仓库 工具&#xff1a;IntelliJ IDEA 2020.2.3 x64 1.作为资源文件夹内的资源引用 1.在项目根路径新建文件夹lib, 将资源文件复制到该文件夹。 2.将文件夹设置为资源文件夹&#xff1a;选中文件夹lib右键 -> Mak…