收藏自:http://blog.163.com/jekyll_zhou@126/blog/static/182047382012103033035925/
最近在项目中发现一个很奇怪的问题,将ProductDraftDO对象传输到远程服务上,远程服务获取的ProductDraftDO对象的ActionTrace为null。而在传输之前明明是有值的。ActionTrace类已经实现了序列化接口,它的所有属性都是可序列化的。
最后查明了原因,是序列化的问题。由于项目中的远程服务用dubbo实现,Hessian是dubbo的默认序列化协议,它比java的序列化性能要高很多。当hessian序列化一个对象时,默认的序列化类是com.caucho.hessian.io. JavaSerializer。
JavaSerializer的writeObject方法代码片段如下:
try {
out.writeMapBegin(cl.getName());
for (int i = 0; i < _fields.length; i++) {
Field field = _fields[i];
out.writeString(field.getName());
out.writeObject(field.get(obj));
}
out.writeMapEnd();
} catch (IllegalAccessException e) {
throw new IOException(String.valueOf(e));
}
将属性和属性值都写入到流中,这里看起来似乎没有什么问题,可是这些Field是怎么来的呢?看看JavaSerializer的构造函数:
public JavaSerializer(Class cl) {
_writeReplace = getWriteReplace(cl);
if (_writeReplace != null) _writeReplace.setAccessible(true);
ArrayList primitiveFields = new ArrayList();
ArrayList compoundFields = new ArrayList();
for (; cl != null; cl = cl.getSuperclass()) {
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) continue;
// XXX: could parameterize the handler to only deal with public
field.setAccessible(true);
if (field.getType().isPrimitive() || field.getType().getName().startsWith("java.lang.")
&& !field.getType().equals(Object.class)) primitiveFields.add(field);
else compoundFields.add(field);
}
}
ArrayList fields = new ArrayList();
fields.addAll(primitiveFields);
fields.addAll(compoundFields);
_fields = new Field[fields.size()];
fields.toArray(_fields);
}
获取当前class的所有字段,接着获取父类的所有字段。序列化的时候,所有字段都放在一个ArrayList里,然后依次写入到二进制流中,反序列化的时候,所有字段放在了一个HashMap里,HashMap的key不能重复,悲剧就出现了,如果子类和父类有同名的字段就会有问题,父类的值会把子类的值覆盖掉。
看看反序列化时,JavaDeserializer的getFieldMap方法,父类字段会把子类字段覆盖掉。
/**
* Creates a map of the classes fields.
*/
protected HashMap getFieldMap(Class cl)
{
HashMap fieldMap = new HashMap();
for (; cl != null; cl = cl.getSuperclass()) {
Field []fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (Modifier.isTransient(field.getModifiers()) ||
Modifier.isStatic(field.getModifiers()))
continue;
else if (fieldMap.get(field.getName()) != null)
continue;
// XXX: could parameterize the handler to only deal with public
try {
field.setAccessible(true);
} catch (Throwable e) {
e.printStackTrace();
}
fieldMap.put(field.getName(), field);
}
}
return fieldMap;
}
然后根据字段接收值,看看JavaDeserializer的readMap方法的代码片段
while (!in.isEnd()) {
Object key = in.readObject();
Field field = (Field) _fieldMap.get(key);
if (field != null) {
Object value = in.readObject(field.getType());
try {
field.set(obj, value);
} catch (Throwable e) {
IOException e1 = new IOException("Failed setting: " + field + " with " + value + "\n"
+ e.toString());
e1.initCause(e);
throw e1;
}
} else {
Object value = in.readObject();
}
}
发送二进制数据时,子类数据在前,父类数据在后。
接收二进制数据时,子类数据在前,父类数据在后。
所以对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失。分析完了,再回过头来看看,ProductDraftDO有一个ActionTrace类型字段actionTrace,父类ProductDO也有一个ActionTrace类型字段actionTrace。由于反序列化时,ProductDraftDO的actionTrace一直被ProductDO的actionTrace覆盖,所以ProductDraftDO的actionTrace总是空的。至于为什么父类和子类都有一个ActionTrace,这是历史原因,这里不讨论。这个问题最初由海滔发现,我对代码做了一个完整的分析。
所以,使用hessian序列化时,一定要注意子类和父类不能有同名字段。在使用dubbo时如果没有指定序列化协议,则也要注意这个问题。
建议dubbo对这个做强制检查。
相关推荐
例如,服务器端可以使用Hessian序列化响应数据,客户端则通过反序列化接收到的二进制流来获取对象。这使得通信过程更为高效,尤其在数据量较大时,相比于文本格式,二进制格式能显著减少网络传输时间。 Hessian工具...
- 首先,Hessian序列化器会遍历Java对象的所有字段,对每个字段进行处理。 - 对于基本类型,如int、boolean、double等,Hessian有专门的编码方式,直接将其转换为字节。 - 对于复杂类型如对象和数组,Hessian会...
《Hessian序列化规范详解》 在分布式系统中,数据传输是不可或缺的一部分,而序列化与反序列化作为数据传输的基础,扮演着至关重要的角色。Hessian,作为一种高效的二进制序列化协议,由Caucho公司开发,广泛应用于...
【S25-Hessian反序列化1】是一个关于Java中Hessian序列化库的讨论,主要涉及Hessian与原生Java序列化的差异以及在Spring框架中的应用。Hessian是一种二进制序列化协议,旨在提高远程过程调用(RPC)的效率。与原生Java...
Java序列化和Hessian序列化的差异 Java序列化和Hessian序列化是两种常用的序列化机制,它们都可以将对象转换为字节流,以便在网络上传输。但是,两者之间有着很大的差异,今天我们就来比较一下它们的实现机制和特点...
《Hessian 2.0序列化协议规范》 在分布式计算和网络通信中,数据的序列化和反序列化是至关重要的环节。Hessian 2.0是一种高效的二进制序列化协议,它旨在减少网络传输的数据量,提高数据交换的效率。本文将详细介绍...
这一安全问题的核心在于 Nacos JRaft 在处理来自客户端的特定构造的数据时未能正确地验证或过滤这些数据,从而允许攻击者通过发送恶意构造的 Hessian 数据包来触发反序列化操作,进而执行任意代码。 #### 三、漏洞...
removal RCE、Hessian 反序列化、Yaml反序列化、密码解密、部分常用敏感路径(漏洞更新截止2024.9.12)
在Spring框架中集成Hessian是为了实现远程方法调用(Remote Method Invocation, RMI),这是一种轻量级的序列化协议,可以高效地传输Java对象。Hessian使得服务提供者和服务消费者之间能够通过网络进行快速的数据...
2. **简单类型支持**:Hessian支持基本的Java数据类型,如整型、浮点型、字符串、日期等,并且可以序列化和反序列化复杂对象。 3. **流式传输**:Hessian协议允许数据分块传输,这意味着服务端可以立即响应部分结果...
这样,客户端通过网络发送一个Hessian序列化的请求,服务端接收后反序列化为本地对象,然后执行相应的操作,再序列化结果返回给客户端。 在实际应用中,Hessian主要应用于以下场景: 1. **分布式服务调用**:通过...
java hessian-3.0.38.jar。修改了原生的jar包,解决了hessian 序列化BigDecimal的精度问题。注意,请在hessian服务端和客户端中分别替换此jar包哦!! 只替换服务端hessian jar包还是会有问题。
服务端使用Hessian序列化`Person`对象,客户端则接收这个二进制流并反序列化为`Person`对象。这可以通过Hessian提供的库来实现,例如在Java中,我们可以使用Caucho的Hessian库。 在实际应用中,Hessian常用于微服务...
Hessian的目标是提供一种快速、简洁的数据序列化和远程方法调用(RPC)机制。在这个简单的demo中,我们将探讨Hessian的核心概念,以及如何在实际应用中使用它。 首先,我们需要理解什么是数据序列化。数据序列化是...
这包括设置服务器端和客户端的Hessian服务,定义服务接口,处理序列化和反序列化的过程,以及调试可能出现的问题。在进行版本升级时,要注意兼容性问题,确保旧版本的服务仍能正常工作,同时充分利用新版本带来的...
- 虽然Hessian提供了高效的通信方式,但也要注意安全性问题,如防止未授权的远程调用。 - 对于敏感数据,可以使用HTTPS进行加密传输,以保护数据的安全。 - 另外,可以通过缓存代理对象和批量处理请求等方式...
2. **序列化/反序列化问题**:Hessian基于二进制序列化,如果Spring中的bean或者数据模型在序列化过程中出现问题,可能导致服务调用失败或者数据解析错误。例如,自定义类型的序列化没有正确实现,或者某些字段在反...
这涉及到对Java对象的Hessian序列化,即将Java对象转化为Hessian二进制格式,以便通过网络发送。例如,在HessianDemo中,可能会有一个包含简单数据类型(如int、String)和自定义对象的业务逻辑方法,这些方法可以...
为了能够通过Hessian进行序列化和反序列化,实体类需要遵循特定的规则,例如字段类型要简单,避免复杂的嵌套结构。 3. **服务发布**: - 在服务器端,需要将实现类绑定到一个特定的URL,以便客户端可以通过该URL...