⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/w605283073/article/details/131270338 「明明如月学长」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

有一位同事说使用 fastjson 进行 JSON 序列化存储到数据库后,发现 JSON 字符串“莫名其妙地”多了一些属性!帮看了下代码,看到基本类型的布尔类型以 is 开头的属性,再看到 fastjson ,就有点想笑。

复现

定义 MyClass

public class MyClass {

// boolean 类型的属性
private boolean isActive;
private boolean valid;

// int 类型的属性
private int id;

// 默认构造器
public MyClass() {
}

// 带有所有属性的构造器
public MyClass(boolean isActive, boolean valid, int id) {
this.isActive = isActive;
this.valid = valid;
this.id = id;
}

// isActive 的 getter 和 setter 方法
public boolean isActive() {
return isActive;
}

public void setActive(boolean isActive) {
this.isActive = isActive;
}

// valid 的 getter 和 setter 方法
public boolean getValid() {
return valid;
}

public void setValid(boolean valid) {
this.valid = valid;
}

// id 的 getter 和 setter 方法
public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}

编写测试代码:

import com.alibaba.fastjson.JSON;

public class MyClassMain {
public static void main(String[] args) {
// 创建 MyClass 对象
MyClass myClass = new MyClass(true, false, 123);

// 使用 fastjson 序列化对象
String jsonString = JSON.toJSONString(myClass);

// 打印 JSON 字符串
System.out.println(jsonString);
}
}

结果:

{“active”:true,“id”:123,“valid”:false}

我们发现多了一个 active 属性,少了一个 isActive 属性!

分析

通过调试可以发现,问题出现在下面这个函数:

com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(java.lang.Class<?>)

public final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
String className = clazz.getName();
long hashCode64 = TypeUtils.fnv1a_64(className);
if (Arrays.binarySearch(denyClasses, hashCode64) >= 0) {
throw new JSONException("not support class : " + className);
}

// 关键
SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
return MiscCodec.instance;
}

return createJavaBeanSerializer(beanInfo);
}

而 buildBeanInfo 的关键是com.alibaba.fastjson.util.TypeUtils#computeGetters

public static List<FieldInfo> computeGetters(Class<?> clazz, //
JSONType jsonType, //
Map<String,String> aliasMap, //
Map<String,Field> fieldCacheMap, //
boolean sorted, //
PropertyNamingStrategy propertyNamingStrategy //
){
// 省略部分代码

if(methodName.startsWith("is")){
if(methodName.length() < 3){
continue;
}
if(returnType != Boolean.TYPE
&& returnType != Boolean.class){
continue;
}
char c2 = methodName.charAt(2);
String propertyName;
Field field = null;
if(Character.isUpperCase(c2)){
if(compatibleWithJavaBean){
propertyName = decapitalize(methodName.substring(2));
} else{
propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
}
// 这里 isActive 的属性名被计算出 active
propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 2);
}

// 省略其他

JSONField fieldAnnotation = null;
if(field != null){
fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
if(fieldAnnotation != null){
if(!fieldAnnotation.serialize()){
continue;
}
ordinal = fieldAnnotation.ordinal();
serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
if(fieldAnnotation.name().length() != 0){
//关键: 使用 JSONField 注解设置的 name 替代属性名
propertyName = fieldAnnotation.name();
if(aliasMap != null){
propertyName = aliasMap.get(propertyName);
if(propertyName == null){
continue;
}
}
}
if(fieldAnnotation.label().length() != 0){
label = fieldAnnotation.label();
}
}
}

// 省略部分代码

FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
annotation, fieldAnnotation, label);
fieldInfoMap.put(propertyName, fieldInfo);
}
}
Field[] fields = clazz.getFields();
computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
return getFieldInfos(clazz, sorted, fieldInfoMap);
}

其实 fastjson 通过反射虽然有能力识别真实的属性名,但是实际操作时会根据 getter 方法反推出属性名,造成转为 JSON 字符串时和实际属性名存在偏差。

解决办法

遵循阿里巴巴 Java 开发手册

孤尽老师的《Java 开发手册》 中专门强调任何布尔类型的变量都不要加 is 前缀,基本类型布尔属性反向解析时,会误以为不带 is 导致获取不到属性,抛出异常。

使用别名

使用 fastjson 自带的 @JSONField 注解,不过这种方式 fastjson 的侵入性太强。

public class MyClass {

@JSONField( name="isActive")
// boolean 类型的属性
private boolean isActive;
private boolean valid;

// 省略其他

}

总结

我们遇到问题时,一定不要止步于解决问题,而是应该寻找最合理的解决方案。比如虽然加上 @JSONField 可以“解决问题”,但侵入性太强,假如其他人也用这个对象使用其他 JSON 序列化工具,就会出问题,这并不是一个好的方案。

AI 时代,遇到问题自己如果不能快速解决时,可以考虑寻求 AI 的帮助。不过使用 AI 时一定要将问题交代清楚。很多同学说的问题连其他同事都听不懂,更不别说 AI 了。

文章目录
  1. 1. 复现
  2. 2. 分析
  3. 3. 解决办法
    1. 3.1. 遵循阿里巴巴 Java 开发手册
    2. 3.2. 使用别名
  4. 4. 总结