java 自定义注解 生成json_用自定义注解实现fastjson序列化的扩展

这篇文章起源于项目中一个特殊的需求。由于目前的开发方式是前后端分离的,基本上是通过接口提供各个服务。

而前两天前端fe在开发中遇到了一些问题:他们在处理字符串类型的时间时会出现精度丢失的情况,所以希望后台是以时间戳的形式返回给前端。而与此同时后台的设计是这个样子的:所有的时间在数据库中均保存为varchar类型,在序列化的时候也是按String字符串去处理的。

这样一来就需要一些解决方案:

1. 所有数据库的时间字段都用timestamp替换,这个是最简单确实实现代价最高的一种方案,由于数据库表太多并且涉及多处的耦合,此方案不可行。

2. 通过fastjson序列化层去转换类型:首先想到的是能不能通过fastjson自己提供的注解方式实现,后调研之后发现,目前使用的版本并不支持;然后想到可以通过fastjson提供的序列化器重载实现String类型的拦截并做处理。但是这种方案会把所有String的字段都执行这段逻辑,并且还要通过固定的format确定哪些是日期,这里非常影响性能;最后决定添加自定义注解,在需要这种类型转换的字段上加上自定义的@StringToDate注解,然后在序列化执行的入口,给所有加了此注解的类提供继承并实现扩展的序列化器,完成自定义的序列化过程。

下面就详细说一下这个过程,一是对fastjson源码做一个解析和说明,二也是对自己的工作做一个总结。

1. 如何实现扩展

首先来看一下自定义的注解,很简单:

c171382677011c63c8c5e08076bf75e2.png

关于这个注解的用法和定义,我就不细说了,Retention指定了他的作用域,而Target说明该注解用于field字段上。

然后既然是要扩展fastjson的功能,我们就来看一下fastjson的入口,首先是WebMvcConfigurerAdapter这个类,我们可以通过继承和重写该类的方法实现MVC的配置,比如拦截器、资源处理器等。我们重写的方法是configureMessageConverters,这个是用来配置信息转化的converter:

ffb21483eea69d49547a8f1aa23fe733.png

可以看到,我们通过这个方法,调用了父类,同时加入了fastJson的converter,然后我们来看看FastJsonMessageConverter这个我们自己写的类:

67a8dc46bd2241b2b6610199b3aa9cb8.png

只是很简单的通过继承实现了自定义config的注入,然后再来看看我们自己写的这个config:

047cc779f4bd63d1e611cd18705ba963.png

这里是最关键的地方,SerializeConfig提供一个入口方法getObjectWriter可以用来对传入的类型进行处理,并为相应的类型设置对应的Serializer序列化器。这里可以看到,我通过判断传入class的注解判断是不是要处理的类型,如果是需要转换的,就用我们写的ExtendJavaBeanSerializer去处理他。(同时可以看到,这里我做了缓存处理,每次的class在处理之后都会通过父类的put方法放入缓存,这样可以大大减少遍历和判断的次数,提高处理性能)

那现在就要看看这个ExtendJavaBeanSerializer做了什么:

f42858a04a907bb66f3203cec57b3ee3.png

这里我们继承了JavaBeanSerializer并重写了processValue方法。这里要说个点:因为每个序列化器都有write方法,所以最开始直观的想法是重写这个方法实现扩展,但是由于write方法很长很长(有250多行),作为切入点非常不方便,然后仔细观察源码,发现其中有这么一句:

propertyValue = this.processValue(serializer, fieldSerializer.fieldContext, object, fieldInfoName,propertyValue);

这句看上去好像就是给我们处理value值的,再点进去一看,processValue居然是个protected方法。那正如意!这就是给我们做扩展的入口,最后便重写了这个方法,并在其中实现了StringToDate的转换逻辑。

2. fastjson源码思想

其实呢,到这里应该会有一个疑问,fastjson提供的预设序列化器有很多很多,看源码发现一个包下面满满都是。那么我们是如何确定要继承这个JavaBeanSerializer的呢?这就需要看fastjson的源码了,看看它究竟是怎么分配和执行这个序列化器的:

首先是SerializeConfig这个类:这个类的config中预设了大量的类型和序列化器的对应关系,他原有的getObjectWriter入口是这样的逻辑: 先判断传入类是不是config中有的,如果有直接给对应的Serializer,如果没有,又会有一堆特殊类(比如集合,异常,编码等类)的判断,而他们也有对应的Serializer。

那如果还没有(比如自己的javabean),在最后会执行一个:createJavaBeanSerializer(clazz) 的方法,在这个方法中,默认会执行createASMSerializer方法,asm查阅资料会发现是一个老外写的字节码序列化器,是序列化最底层的一个工具,很多开源序列化包都是对这个进行封装实现的。那我们要继承JavaBeanSerializer这个类并使用自己的,就说明我们不想让他执行createASMSerializer这个方法,为什么呢?

因为在createASMSerializer里面的逻辑是:当序列化对象 > 256个时,会创建可继承的JavaBeanSerializer去处理,如果<256,则不会给他任何序列化器,而是直接通过反射方式在内存中(就是生生拼出了一个序列化过程)实现对这个对象的处理。关键代码如下:

158abf8ca53cc468ecfe8239cd4fc590.png

当然,下面还有很长很长的内存字节码操作的逻辑,我就不贴出了。我想这可能也是fastjson快的一个原因吧。

写到这里,关于对fastjson的扩展就差不多说完了,其实fastjson的源码中其他类中也都有自己独特的优化方式,有些还是挺有意思的。另外呢,我惊奇地发现,fastjson的作者和durid居然是一个人,这个来自阿里的wenshao还真的厉害啊。作为一个学习者,只是希望有朝一日也能写出这么漂亮的代码吧,希望自己可以不断努力!

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

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

相关文章

工厂模式 java场景_研磨设计模式之简单工厂模式(场景问题)

简单工厂不是一个标准的设计模式&#xff0c;但是它实在是太常用了&#xff0c;简单而又神奇&#xff0c;所以还是需要好好掌握的&#xff0c;就当是对学习设计模式的热身运动吧。为了保持一致性&#xff0c;我们尽量按照学习其它模式的步骤来进行学习。1 场景问题大家都知道&…

java asm jndi_GitHub - Q1ngShan/JNDI: JNDI 注入利用工具

JNDI 注入利用工具介绍本项目为 JNDI 注入利用工具&#xff0c;生成 JNDI 连接并启动后端相关服务&#xff0c;可用于 Fastjson、Jackson 等相关漏洞的验证。本项目是基于 welk1n 的 JNDI-Injection-Exploit&#xff0c;在此项目的基础服务框架上&#xff0c;重新编写了攻击利用…

java保存登录信息_java – 保存登录详细信息(首选项)android

我有一个具有登录,注销功能的Android应用程序.登录表单包含用户名和密码以及登录按钮.我想在用户选中“记住我”复选框时保存用户名和密码.我的project.java文件如下所示&#xff1a;public class project extends Activity {private static final int IO_BUFFER_SIZE 4 * 102…

java堆和非堆_java 堆与非堆 内存

堆(Heap)和非堆(Non-heap)内存按照官方的说法&#xff1a;“Java 虚拟机具有一个堆&#xff0c;堆是运行时数据区域&#xff0c;所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主…

java中fis和fos_java中-的流-与操作

/*字节输出流 OutputStrema&#xff1a;* OutputStream抽象类* write(int b); 将指定的字节写入此流中* write(byte[] b); 将指定的数组 输入此流中* write(byte[] b , int a , int c); 将指定的数组输入此流中 从a索引开始 获取c 个* close(); 将此流关闭 并释放资源* fl…

java 上传文件及预览_SpringBoot上传下载文件及在线预览

SpringBoot上传下载文件及在线预览今天大概就说说如何使用SpringBoot进行上传和下载以及在线预览文件 本篇主要介绍上传下载的功能&#xff0c;对于界面就简单一点&#xff0c;大致如下&#xff1a;一、老规矩还是先看看小项目的目录结构&#xff1a;二、添加对应的pom依赖org.…

yolov4用1050ti_简单粗暴的多目标跟踪神器 – DeepSort

目标跟踪问题一直是计算机视觉的热点任务之一&#xff0c;简单的可以分为单目标跟踪与多目标跟踪&#xff0c;最常见的目标跟踪算法都是基于检测的跟踪算法&#xff0c;首先发现然后标记&#xff0c;好的跟踪算法必须具备REID的能力。今天小编斗胆给大家推荐一个结合传统算法跟…

java 数据库语句_java连接各数据库的语句

1、Oracle8/8i/9i数据库(thin模式)Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();String url"jdbc:oracle:thin:localhost:1521:orcl";//orcl为数据库的SIDString user"test\";String password"test";Connection c…

alm数据库mysql_mysql

1. mysql启动mysqld --console2. mysql关闭mysqladmin shutdown -uroot -proot3. 免安装mysql配置&#xff1a;启动mysql&#xff1a; mysqld --skip-grant-tables(跳过安全校验)&#xff0c; 修改mysql.user表里面的数据&#xff0c;最后flush privileges;mysql jdbc连接代码…

类java的步骤_java类加载的过程

类加载就是三个过程&#xff1a;加载、链接、初始化链接又可以分为验证、准备、解析1.加载将class字节码文件通过类加载器装入内存中2.验证确保当前class文件的字节流所包含的内容符合当前JVM的规范要求&#xff0c;并且不会出现危害JVM自身安全的代码&#xff0c;当前字节流不…

java拓展接口_Java拓展接口-default关键词

Java接口在使用过程中有两点规定&#xff1a;1、接口中只能有定义方法名、方法返回类型&#xff0c;不能有方法的实现。2、实现接口的类&#xff0c;必须实现接口中所有的方法。例如下面的例子&#xff1a;//定义接口public interface Action {//接口中的方法定义&#xff0c;只…

java编写通信录管理系统_Java 实现通讯录管理系统教程

本文实例为大家分享了java实现通讯录管理系统的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下完成项目的流程&#xff1a;1.根据需求&#xff0c;确定大体方向2.功能模块分析3.界面实现4.功能模块设计5.coding6.代码测试下面是源代码&#xff1a;import java.awt.Co…

java姑娘_初识java这个小姑娘(二)

正版疯狂java讲义第5版编程教材76.5元(需用券)去购买 >妙解垃圾回收机制周一&#xff0c;早高峰&#xff01;&#xff01;&#xff01;五个字&#xff0c;说尽心中的绝望&#xff01;&#xff01;&#xff01;一段考验一个人耐力、智力、开车技术以及脾气的路。 我把车开进了…

JAVA script 循环 图片_深入分析JavaScript 事件循环(Event Loop)

事件循环(Event Loop)&#xff0c;是每个JS开发者都会接触到的概念&#xff0c;但是刚接触时可能会存在各种疑惑。众所周知&#xff0c;JS是单线程的&#xff0c;即同一时间只能运行一个任务。一般情况下这不会引发问题&#xff0c;但是如果我们有一个耗时较多的任务&#xff0…

mysql update 跳过重复_MySQL通过UPDATE / DELETE整合重复的数据记录

我有一个看起来像这样的表&#xff1a;mysql> SELECT * FROM Colors;╔════╦══════════╦════════╦════════╦════════╦════════╦════════╦════════╗║ ID ║ USERNAME ║ RED ║ GREEN ║ YELLOW ║ …

java目录实用工具_JAVA 创建文件和文件夹,删除文件和文件夹的实用工具

package com.file;import java.io.File;import java.io.IOException;//创建新文件和目录public class CCRDFile {// 验证字符串是否为正确路径名的正则表达式private static String matches "[A-Za-z]:\\\\[^:?\">// 通过 sPath.matches(matches) 方法的返回值判…

java 方法重载的作业_java第六章 方法及方法重载 课堂笔记、作业

当参数传递为基本数据类型时&#xff0c;参数变化不保留&#xff0c;基本数据类型参数传值当参数传递为引用数据类型时&#xff0c;参数变化会保留&#xff0c;引用数据类型参数传址//基本数据类型在别处被重新赋值&#xff0c;则本体不受影响&#xff0c;其值不变//引用型数据…

java旋转图片后边上变黑_Java旋转图像将背景的一部分变成黑色

因此,我下载了“原始”图像(不是正方形),对其进行了修改,使其变为正方形,运行您的代码,得到了java.awt.image.ImagingOpException&#xff1a;无法转换src图像异常,将BufferedImage.TYPE_INT_RGB更改为BufferedImage .TYPE_INT_ARGB并得到…import java.awt.geom.AffineTransfo…

python 数据库连接池_【转】Python 数据库连接池

python编程中可以使用pymysql进行数据库连接及增删改查操作&#xff0c;但每次连接mysql请求时&#xff0c;都是独立的去请求访问&#xff0c;比较浪费资源&#xff0c;而且访问数量达到一定数量时&#xff0c;对mysql的性能会产生较大的影响。因此实际使用中&#xff0c;通常会…

python中location_使用python请求模块时的LocationValueError

我一直在使用请求包与Web进行交互&#xff0c;过去没有任何问题 . 最近当使用我大约一周没用过的脚本时&#xff0c;我在执行例程 requests.get() 调用时遇到以下错误&#xff1a;LocationValueError: No host specified.背景研究经过大量的谷歌搜索 python requests LocationV…