序列化和反序列化是日常工作中经常使用的工具,一般用于对象的持久化保存或者对象的网络传输,一般有两种情况,一种是对象本身实现了Serializable接口,这种情况下可以利用jdk自带的功能或者Kryo等这种封装好的序列化反序列化工具,还有一种情况就是对象本身并没有实现Serializable接口,那么这个时候开发者们就会考虑将对象序列化为字符串来进行持久化保存或传输。
笔者在日常开发中常用到的序列化工具就是谷歌的Gson和阿里巴巴的FastJson,这两种的区别在上一篇博客中通过实验的方式做了一个简单对比。那么本篇来着重探究一下Gson是如何反序列化对象的。
我们先来考虑一个问题,如果有这样一个json字符串{“name”:“cz”,“age”:18},在没有现成的反序列化工具时,我们要如何将该json字符串中的内容映射到对象中呢?
对象的结构如下:
public class Student {
public String name; // 特别注意这里修饰符为public
public String home;
public Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHome() {
return home;
}
public void setHome(String home) {
this.home = home;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
通过反射调用set方法来设置值。
代码如下:
/**
* @author Chengzhi
* @date 2023-08-23
* @测试目的:测试通过set方法来反序列化对象
* @预期结果:
*/
@Test
public void test2() throws IllegalAccessException, InstantiationException, InvocationTargetException {
String json = "{"name":"cz","age":18}";
String[] fields = json.substring(1,json.length()-1).split(",");
Map map = new HashMap();
for (String field : fields) {
String[] split = field.split(":");
String key = split[0];
String value = split[1];
if (key.startsWith(""") && key.endsWith(""")) {
key = split[0].substring(1,split[0].length()-1);
}
if (value.startsWith(""") && value.endsWith(""")) {
value = split[1].substring(1,split[1].length()-1);
}
map.put(key.toUpperCase(), value);
}
Class studentClass = Student.class;
Student o = studentClass.newInstance();
Method[] methods = studentClass.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (methodName.startsWith("set")) {
String key = methodName.substring(3, methodName.length()).toUpperCase();
Class> parameterType = method.getParameterTypes()[0];
Object value = map.get(key);
if ("java.lang.Integer".equals(parameterType.getName())) {
value = Integer.valueOf((String) value);
}
method.invoke(o, value);
}
}
System.out.println(o.getName());
}
在本案例中由于对象的变量是使用public修饰的,那么同样的我可以通过反射直接赋值。
代码如下:
/**
* @author Chengzhi
* @date 2023-08-24
* @测试目的: 通过反射直接给对象的变量赋值
* @预期结果:
*/
@Test
public void test3() throws IllegalAccessException, InstantiationException {
String json = "{"name":"cz","age":18}";
String[] jsonFields = json.substring(1,json.length()-1).split(",");
Map map = new HashMap();
for (String field : jsonFields) {
String[] split = field.split(":");
String key = split[0];
String value = split[1];
if (key.startsWith(""") && key.endsWith(""")) {
key = split[0].substring(1,split[0].length()-1);
}
if (value.startsWith(""") && value.endsWith(""")) {
value = split[1].substring(1,split[1].length()-1);
}
map.put(key.toUpperCase(), value);
}
Class studentClass = Student.class;
Student o = studentClass.newInstance();
Field[] fields = studentClass.getFields();
for (Field field : fields) {
String name = field.getName();
Object value = map.get(name.toUpperCase());
if ("java.lang.Integer".equals(field.getGenericType().getTypeName())) {
value = Integer.valueOf((String) value);
}
field.set(o, value);
}
System.out.println(o.getName());
}
上述方式一其实就是阿里巴巴的FastJson实现原理,而第二种实现方式由于示例POJO类中的变量都是使用public修饰的,可以直接反射赋值,但是,正常的对象都是不允许使用public修饰,因为这样值很容易被改变。如果pojo类中的变量使用private修饰,由于访问权限的约束,Field[] fields = studentClass.getFields();这里会获取不到参数值。最终不会赋值成功。
其实Gson反序列化原理和方式二思路很接近,都是直接去操作POJO类的全局变量,而不是去调用set方法,那么Gson是如何绕过变量修饰符的权限约束呢。从源码可以发现Gson是利用Unsafe类的putObject方法。
//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
该方法可以忽略修饰符的权限约束。
Gson利用反射的思想结合强大的Unsafe类,通过直接改变类的变量值来达到反序列化的目的。