草庐IT

今天,念念不忘一件事... ✅在使用Fastjson-JSON#toJSONString序列化对象时,如何截取比较长的属性值串?

buguge - Keep it simple,stupid 2023-04-19 原文

项目里 前后端页面的http请求 及 dubbo服务间的RPC调用,返回值类型统一是一个 Result<T> ,其结构如下。

@Data
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 返回处理消息
     */
    private String message = "操作成功!";

    /**
     * 返回code码
     */
    private Integer code = 0;

    /**
     * 返回数据对象 data
     */
    @Getter
    private T data;
}

 

各项目间使用dubbo作RPC调用。我们在底层定义了一个公用的DubboTraceFilter。这个Filter会将项目的dubbo接口方法的请求参数和返回值打印到log文件里。其中,返回值 Result<T>对象 通过fastjson序列化。

 

背景介绍完毕。接下来说我要解决的事情。

注意到Result<T>的data字段。它是泛型T的实例,就是说,这个data会是任意类型的数据。

从log里看,当data里是集合数据,例如,分页查询的场景,打印出来的log会超级长。

这导致日志量很大,同时,这种无用的日志刷屏,也不利于我们排查问题。

 

简言之,看下面两段Result<T>对象序列化的json串(为便于阅读,进行了格式化),我希望log里出现后者这种缩减版的文本。

{
    "message": "成功",
    "code": 200,
    "data": ["0memob92142f2-ad8a-4812-913e-002f8f9d1894", "1memo77d4ad82-078f-4f73-a26e-c5302a596042", "2memoa69185c2-670d-480b-b1d2-19fd1326ecd5", "3memoee5d13a7-83bd-4430-a4b0-198e65201dc7", "4memo519d9d69-a27f-4864-8dd4-889ada1790a3", "5memo85034936-564b-41d8-94f0-ff1ac7be8d92", "6memoa22d4b20-828a-4ac5-a3fe-461283fc4154", "7memo7b2b8880-80b2-41f8-93d9-553467287e13", "8memo55afe9f2-e6b5-481c-9978-773fb5ff0f14", "9memoa5a92ffd-4e72-42f1-8d81-7221d2f371a3"],
    "timestamp": 1666961782888
}
{
    "code": 200,
    "message": "成功",
    "data": "[\"0memob92142f2-ad8a-4812-913e-002f8f9d1894\",\"1memo77d4ad82-078f-4f73-a26e-c5302a596042\",\"2memoa6...",
    "timestamp": 1666961782888
}

 

 

那么,如何解决这个痛点?

我相信,找开发组里的任何一位同学,他都能解决。改DubboTraceFilter里的代码就行了,对序列化的json串进行相关截取。

 

而我想说什么呢?

 

一劳永逸!

程序里有还有其他地方也存在通过 JSON#toJSONString(Result<T>) 打印log的代码。 难道逐个改吗?

所以,有没有简单的办法,改一处就全改了。

 

世上无易事,用心求精进。只要不放弃,办法就会有。

解决办法是 利用fastjson的两个成员 -- com.alibaba.fastjson.serializer.ObjectSerializer 和 com.alibaba.fastjson.annotation.JSONField 。

fastjson的ObjectSerializer 是fastjson的序列化器接口,相应地,ObjectDeserializer是反序列化接口。在com.alibaba.fastjson.serializer包下,有许多默认的XxxCoDoc或XxxSerializer实现,如StringCodec、ToStringSerializer、IntegerCodec、EnumSerializer等。(Codec:CoDec,Compressor Decompressor,压缩解压缩器,编码解码器) XxxCodec会同时实现 ObjectSerializer 和 ObjectDeserializer两个接口,XxxSerializer只实现ObjectSerializer接口。

 

ObjectSerializer 和 ObjectDeserializer这两个接口类的javadoc,写明了如何使用它们。

首先,通过实现ObjectSerializer接口来自定义一个序列化器 StringAbbreviatingSerializer,用以截断属性值对应的字符串。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.lang.reflect.Type;

public class StringAbbreviatingSerializer implements ObjectSerializer {
    /**
     *
     * @param serializer
     * @param object field的值
     * @param fieldName field的name
     * @param fieldType field的类型,如java.lang.String
     * @param features
     * @throws IOException
     */
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
        serializer.write( StringUtils.abbreviate(JSON.toJSONString(object),100));
    }

}
View Code

 

然后,使用com.alibaba.fastjson.annotation.JSONField 注解。

    @JSONField(serializeUsing = StringAbbreviatingSerializer.class)
    private T data;

 

完了吗?我要补充——一并重写Result<T>的toString方法。彻底一劳永逸。

@Data
public class Result<T> implements Serializable {

    。。。。
    
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

 

 

这么改,对别的地方有影响吗?

要说明的是,加上这个注解后,对与http请求的响应值Result<T> 和 dubbo的响应值Result<T> 是不会有任何影响的。为什么?因为这两者的序列化不是fastjson,SpringMVC默认使用Jackson作为序列化工具;dubbo RPC默认启用的序列化方式是hessian2(实际不是原生的hessian2序列化,而是阿里修改过的hessian lite)。以上已亲测。

所以,放心用,放心使用fastjson  JSON#toJSONString(Result<T>) 去记录日志。妈妈再也不用担心我的日志爆屏了。

 

 

over! 

 

附:

使用fastjson提供的接口实现自定义的编解码器

在项目开发中经常会遇到一些业务需要对某些数据进行特殊的定制化处理,fastjson为我们提供了接口可以用于实现自定义的编解码器来完成我们的业务要求。

ObjectSerializer和ObjectDeserializer分别是fastjson的编码器和解码器接口。

 

有关今天,念念不忘一件事... ✅在使用Fastjson-JSON#toJSONString序列化对象时,如何截取比较长的属性值串?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  10. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

随机推荐