
1. 项目缘起为什么我们需要关注FastJson的全局配置如果你在Java后端开发领域摸爬滚打超过一年那么FastJson这个名字对你来说一定不陌生。作为阿里巴巴开源的一款高性能JSON处理库它以“快”著称在无数个需要序列化与反序列化的场景里比如API接口返回、缓存数据存储、消息队列传输都扮演着关键角色。然而正是因为它被用得太多、太广一旦配置不当引发的“坑”往往也让人印象深刻。我最近就遇到了一个典型的场景。一个线上服务在序列化一个包含BigDecimal字段的对象时突然出现了科学计数法如1.0E7的JSON字符串。前端同学看到这个直接懵了因为他们的数字格式化逻辑无法处理这种格式导致页面显示异常。排查下来问题根源就在于我们没有为BigDecimal设置全局的序列化规则。这让我意识到很多团队在使用FastJson时都停留在“能用就行”的阶段对于其强大的、可定制的全局配置能力知之甚少或者即便知道也因为怕麻烦而选择在每次调用时写重复代码。这就是我们今天要深入探讨的主题FastJson的全局配置。它绝不仅仅是设置一两个参数那么简单而是一套完整的、用于统一规范JSON处理行为的机制。通过合理的全局配置你可以统一行为确保整个应用内同一种数据类型如日期、数字的序列化/反序列化格式完全一致避免因开发人员习惯不同导致的“风格污染”。提升安全性全局关闭AutoType等危险特性是防范反序列化漏洞的第一道防线。避免重复代码无需在每个toJSONString或parseObject调用处都写一遍SerializerFeature或Feature。便于维护当需要调整某种数据类型的处理策略时只需修改一处配置而不是搜索替换整个代码库。接下来的内容我将结合自己踩过的坑和最佳实践从如何设置全局参数到不同配置方式的优劣对比再到那些容易让人栽跟头的“天坑”及其排查思路为你完整梳理一遍FastJson全局配置的方方面面。2. 核心配置项详解不只是SerializerFeature提到FastJson配置很多人第一反应就是SerializerFeature。没错它是序列化特性的核心但全局配置的版图远不止于此。我们需要从几个维度来构建完整的配置体系。2.1 序列化配置SerializerFeature这是最常用的一组配置用于控制对象如何被转换成JSON字符串。通过JSON.toJSONString(object, serializerFeatures)传入但更推荐通过全局配置来统一管理。以下是一些关键且容易出问题的特性WriteDateUseDateFormat这是处理日期格式的基石。一旦启用FastJson会使用你通过JSON.DEFFAULT_DATE_FORMAT设置的全局日期格式或者JSONField注解上的format属性。如果不启用这个特性Date类型默认会输出为时间戳毫秒数这常常是前后端联调的第一个坑。// 错误示例未启用WriteDateUseDateFormat日期输出为时间戳 // 输出: {birthday:1672502400000} User user new User(); user.setBirthday(new Date()); String json JSON.toJSONString(user); // 缺少特性 // 正确做法全局启用 JSON.DEFAULT_GENERATE_FEATURE | SerializerFeature.WriteDateUseDateFormat.getMask(); JSON.DEFFAULT_DATE_FORMAT yyyy-MM-dd HH:mm:ss; // 现在输出: {birthday:2023-01-01 12:00:00}WriteMapNullValue决定是否输出值为null的字段。这是一个需要团队达成一致的重要决策。如果关闭默认行为{name: null}在JSON中会变成{}。这可能会影响前端对字段存在性的判断。如果开启则能保持完整的结构信息但会增加传输数据量。我的建议是在内部微服务间调用可以考虑关闭以提升性能但对前端接口最好开启保证数据契约的明确性。WriteBigDecimalAsPlain这就是开篇那个“科学计数法”坑的救星。当BigDecimal的值很大或很小时FastJson默认会使用科学计数法输出。启用此特性后会始终以普通数字字符串的形式输出例如10000000.00而不是1.0E7。对于金融、电商等涉及金额计算的场景这个特性必须全局开启。BigDecimal money new BigDecimal(0.0000001); // 未启用WriteBigDecimalAsPlain: 1E-7 // 启用后: 0.0000001DisableCircularReferenceDetect禁用循环引用检测。默认情况下FastJson会检测对象间的循环引用例如A对象持有B对象B对象又持有A对象并在第二次遇到时输出引用标识如$ref:$以避免栈溢出。但在某些场景下如缓存整个对象图你可能需要禁用此功能。注意禁用后若存在循环引用序列化会直接抛出栈溢出错误。PrettyFormat输出格式化的JSON带缩进和换行。这个特性绝对不要放在全局配置里它只应在调试、日志输出等特定场景下临时使用因为它会显著增加字符串长度影响网络传输性能。2.2 反序列化配置Feature这组配置控制如何将JSON字符串解析回Java对象。SupportAutoType这是安全性的重中之重自动类型识别AutoType是FastJson一个强大但极其危险的功能。它允许JSON字符串通过type字段指定要反序列化的具体类名。攻击者可以利用此特性构造恶意JSON触发任意类的构造方法或getter/setter方法从而执行任意代码例如利用TemplatesImpl链。在FastJson 1.2.68及以后版本此特性默认已关闭。但为了绝对安全你必须在全局配置中显式关闭它并且使用白名单机制ParserConfig.getGlobalInstance().addAccept(com.yourpackage.)来控制允许反序列化的类。// 全局关闭AutoType安全基线 ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 设置白名单只允许特定包下的类进行AutoType反序列化 ParserConfig.getGlobalInstance().addAccept(com.yourcompany.safe.model.);AllowComment允许JSON中存在//或/* */注释。这在解析一些包含注释的配置文件JSON时有用但标准的JSON是不支持注释的。除非有特定需求否则不建议开启。UseBigDecimal当JSON中的数字没有小数点时默认使用BigInteger或Long。启用此特性后所有数字都将被反序列化为BigDecimal可以避免整数溢出问题但会损失一些性能并增加内存占用。需要根据业务数据的范围权衡。IgnoreNotMatch忽略JSON中存在的、但在目标Java类中找不到对应字段的键。默认是false即遇到不匹配字段会抛出异常。设置为true可以提高兼容性但可能会掩盖数据字段名拼写错误等问题。2.3 全局参数设置除了上述特性还有一些重要的全局静态参数JSON.DEFFAULT_DATE_FORMAT全局默认日期格式。需要和SerializerFeature.WriteDateUseDateFormat配合使用。JSON.DEFAULT_GENERATE_FEATURE全局默认序列化特性组合。我们可以通过位操作来设置。JSON.DEFAULT_PARSER_FEATURE全局默认反序列化特性组合。3. 全局配置的三种方式及其适用场景知道了配置项下一步就是如何设置。根据应用的生命周期和架构主要有三种方式。3.1 方式一静态代码块初始化简单直接这是最常见、最直观的方式在应用启动类或一个专门的配置类中使用静态代码块进行设置。public class FastJsonGlobalConfig { static { // 1. 设置全局日期格式 JSON.DEFFAULT_DATE_FORMAT yyyy-MM-dd HH:mm:ss; // 2. 设置全局序列化特性 // 先获取默认特性然后用位或操作添加我们需要的特性 int defaultFeatures JSON.DEFAULT_GENERATE_FEATURE; defaultFeatures | SerializerFeature.WriteDateUseDateFormat.getMask(); defaultFeatures | SerializerFeature.WriteMapNullValue.getMask(); defaultFeatures | SerializerFeature.WriteBigDecimalAsPlain.getMask(); // 用位与操作移除不需要的特性例如确保PrettyFormat不在全局中 defaultFeatures ~SerializerFeature.PrettyFormat.getMask(); JSON.DEFAULT_GENERATE_FEATURE defaultFeatures; // 3. 设置全局反序列化特性关闭AutoType ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 可以在此处添加白名单 // ParserConfig.getGlobalInstance().addAccept(com.xxx.model.); // 4. 可选配置反序列化特性 Feature[] defaultParserFeatures {Feature.AllowComment, Feature.UseBigDecimal}; // 注意Feature的设置方式与SerializerFeature不同通常通过ParserConfig或具体parseObject调用设置 } }优点简单明了集中管理应用启动后立即生效。缺点配置是静态的无法根据不同的HTTP请求或线程进行动态调整。在复杂的多租户或需要不同序列化策略的场景下不够灵活。适用场景大多数单体应用或微服务中对JSON格式有统一、固定要求的场景。3.2 方式二自定义ObjectMapper/HttpMessageConverterSpring Boot集成在Spring Boot应用中更优雅的方式是定制HttpMessageConverter这样所有通过RestController返回的响应和接收的请求都会自动应用你的配置。Configuration public class FastJsonConfig { Bean public HttpMessageConverterObject fastJsonHttpMessageConverter() { // 1. 创建FastJson转换器 FastJsonHttpMessageConverter converter new FastJsonHttpMessageConverter(); // 2. 创建配置对象 com.alibaba.fastjson.support.config.FastJsonConfig fastJsonConfig new com.alibaba.fastjson.support.config.FastJsonConfig(); // 3. 设置序列化特性 fastJsonConfig.setSerializerFeatures( SerializerFeature.WriteMapNullValue, // 输出null字段 SerializerFeature.WriteDateUseDateFormat, // 日期格式化 SerializerFeature.WriteBigDecimalAsPlain // BigDecimal普通数字格式 // 注意不要在这里加PrettyFormat ); // 4. 设置日期格式 fastJsonConfig.setDateFormat(yyyy-MM-dd HH:mm:ss); // 5. 设置反序列化配置关键安全步骤 fastJsonConfig.setParserConfig(globalParserConfig()); // 6. 将配置赋予转换器 converter.setFastJsonConfig(fastJsonConfig); // 7. 设置支持的MediaType converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON)); return converter; } Bean public ParserConfig globalParserConfig() { ParserConfig parserConfig new ParserConfig(); parserConfig.setAutoTypeSupport(false); // 全局关闭AutoType // 设置白名单 // parserConfig.addAccept(com.yourcompany.model.); return parserConfig; } }优点与Spring Boot无缝集成非侵入式只影响HTTP层面的JSON转换不影响代码内直接使用JSON.toJSONString的调用那些调用仍受静态全局配置影响。可以配置多个Converter应对不同场景。缺点只作用于Spring MVC的HTTP消息转换过程。适用场景所有基于Spring Boot的Web应用。这是Spring Boot项目中的推荐做法。3.3 方式三结合使用与上下文感知配置高级在更复杂的场景下你可能需要静态全局配置作为默认值同时在特定场景下如某个API接口某个消息消费者使用自定义配置。这时可以结合使用。// 默认使用全局配置 String defaultJson JSON.toJSONString(obj); // 特定场景需要忽略null值并且美化输出仅用于日志 String prettyJsonForLog JSON.toJSONString(obj, SerializerFeature.PrettyFormat, SerializerFeature.IgnoreNonFieldGetter); // 特定场景序列化给某个老旧前端需要禁用循环引用检测并使用特定日期格式 SimpleDateFormat oldFormat new SimpleDateFormat(MM/dd/yyyy); String customJson JSON.toJSONStringWithDateFormat(obj, oldFormat.toPattern(), SerializerFeature.DisableCircularReferenceDetect);核心原则全局配置设定安全、通用的基线局部调用在必要时覆盖特定配置。务必注意局部调用的特性设置是覆盖而非叠加除非你显式地组合了全局特性。4. 实战爬坑指南那些年我们踩过的FastJson配置坑理论说再多不如踩一次坑记得牢。下面是我总结的几个高频“坑点”及其解决方案。4.1 坑一日期格式混乱前端后端“打架”问题现象后端接口返回的日期字段有时是时间戳有时是格式化字符串导致前端解析失败。根因分析没有全局启用WriteDateUseDateFormat导致默认行为时间戳生效。即使启用了但开发人员在某些业务代码里手动调用了JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat)但传入了不同的日期格式字符串或者更糟根本没传日期格式导致格式不统一。实体类上的JSONField(format...)注解与全局格式冲突。排查与解决确立基准在全局配置中明确设置JSON.DEFFAULT_DATE_FORMAT并启用WriteDateUseDateFormat。这是唯一的真理源。代码审查禁止在业务代码中随意使用JSON.toJSONStringWithDateFormat或带日期特性参数的序列化方法除非有极其特殊的、偏离全局标准的理由并且需要添加详细注释。注解慎用JSONField的format属性优先级高于全局配置。如果团队决定使用全局格式应避免在实体类上分散定义日期格式除非该字段确实需要特殊处理如只显示日期不显示时间。测试验证编写单元测试针对包含Date字段的常用对象进行序列化测试断言其输出格式符合全局预期。4.2 坑二BigDecimal的科学计数法“惊魂”问题现象金额、比例等字段在JSON中变成了1E7、1.23E-4等形式导致前端显示错误或后续计算解析失败。根因分析FastJson为了保持数字的精度和紧凑性默认对超出一定范围的BigDecimal使用科学计数法。这符合JSON数字的规范但不符合大多数业务系统的显示和传输预期。解决方案全局启用WriteBigDecimalAsPlain这是最根本、最一劳永逸的解决方案。确保所有BigDecimal都序列化为普通数字字符串。反序列化一致性如果启用了Feature.UseBigDecimal将所有数字反序列化为BigDecimal那么序列化时启用WriteBigDecimalAsPlain就能保证闭环的一致性。注意精度丢失WriteBigDecimalAsPlain会输出完整的精度。例如new BigDecimal(10.00)会输出10.00而如果将其视为Double可能会输出10.0。这有时是需要的保留金额小数点后两位有时可能不是。要清楚业务意图。4.3 坑三AutoType安全漏洞防线如何构筑问题现象安全扫描报告指出应用存在FastJson反序列化漏洞如CNVD-2019-22238等。根因分析使用了存在漏洞的FastJson版本1.2.68并且没有正确配置安全策略允许了不安全的AutoType行为。彻底解决方案防御纵深升级版本立即升级到FastJson的最新安全版本1.2.83。阿里官方持续在修复安全漏洞新版本通常内置了更严格的安全限制。显式关闭AutoType无论版本新旧必须在应用启动时通过ParserConfig.getGlobalInstance().setAutoTypeSupport(false)显式关闭。不要依赖默认值。使用白名单如果业务确实需要使用AutoType功能例如处理多态类型必须使用白名单机制。ParserConfig config ParserConfig.getGlobalInstance(); config.setAutoTypeSupport(true); // 谨慎开启 // 添加精确的白名单范围尽可能小 config.addAccept(com.yourcompany.safe.dto.BaseResponse); config.addAccept(com.yourcompany.safe.model.AbstractAnimal); // 或者使用包名前缀仍要谨慎 config.addAccept(com.yourcompany.safe.model.);重要提示白名单列表需要严格评审和维护避免引入危险类如java.lang.ProcessBuilder,javax.script.ScriptEngineManager等及其子类。输入验证对接收的JSON字符串进行来源验证和格式检查避免处理不可信的JSON数据。4.4 坑四全局配置“不生效”优先级与作用域陷阱问题现象明明在静态代码块里配置了WriteMapNullValue但某个接口返回的JSON里还是没有null字段。根因分析FastJson的配置存在优先级局部调用会覆盖全局配置。最常见的原因是在某个RestController的方法里或者自定义的HttpMessageConverter里直接创建了新的FastJsonConfig对象并设置了不同的SerializerFeatures而这个新配置没有包含WriteMapNullValue。使用了JSON.toJSONString(object, features)方法并且传入的features参数没有包含全局启用的特性。这个方法调用使用的特性集是完全由传入参数决定的不会自动合并全局特性。排查思路全局搜索代码中对JSON.toJSONString的调用检查传入的特性参数。检查Spring Boot项目中是否定义了多个HttpMessageConverter或者是否有拦截器、过滤器修改了响应内容。使用调试工具在序列化时查看实际的SerializerFeature组合是什么。最佳实践定义一个工具类提供安全的序列化方法确保始终合并全局默认特性。public class JsonUtils { public static String toJsonString(Object object) { // 使用全局默认特性 return JSON.toJSONString(object); } public static String toJsonString(Object object, SerializerFeature... additionalFeatures) { // 合并全局特性和附加特性 int features JSON.DEFAULT_GENERATE_FEATURE; for (SerializerFeature sf : additionalFeatures) { features | sf.getMask(); } return JSON.toJSONString(object, features); } }团队约定所有JSON序列化都通过此工具类进行避免直接调用JSON.toJSONString。5. 配置的维护与演进不是一劳永逸全局配置一旦设定并非高枕无忧。随着业务发展和技术演进你需要关注以下几点版本升级的兼容性检查每次升级FastJson版本都需要仔细阅读官方Release Notes特别是Breaking Changes部分。新版本可能会修改某些特性的默认值或者废弃、移除某些特性。升级后必须对核心序列化/反序列化场景进行回归测试。配置文档化将团队的FastJson全局配置决策包括每个特性开启/关闭的原因、日期格式标准、安全策略等写入项目Wiki或架构决策记录ADR中。这有助于新成员快速理解和遵守规范。监控与告警可以考虑通过AOP等方式对反序列化过程中出现的异常如AutoType被拒绝、字段不匹配进行监控和告警及时发现潜在的错误调用或攻击尝试。定期审计定期检查代码库确保没有绕过全局配置的“野路子”序列化/反序列化代码。可以将此作为代码审查Code Review的一项检查点。FastJson的全局配置就像城市的交通规则。没有它每个司机开发者按自己的习惯开车短期内似乎没问题但迟早会引发混乱和事故。制定一套明确、合理、安全的全局规则并确保所有“司机”都知晓和遵守是构建稳定、可维护、安全的后端服务的基石之一。希望这篇从踩坑到填坑的经验总结能帮助你更好地驾驭FastJson这个强大的工具让它真正为你的系统提速而不是添堵。