现象
服务A提供一个dubbo接口,LoginServer.login(LoginReqDTO reqDTO);
服务B通过rpc调用A提供的LoginService接口的login方法。查看日志发现服务B发起调用时传递的reqDTO参数与服务A接收到的参数部分不一致。
- 例如:服务B请求参数
reqDTO=LoginReqDTO(appKey="appKey", mobile="18812345678", countryCallingCode="+86",password="a12345")
- 服务A接受参数:
reqDTO=LoginReqDTO(appKey="appKey", mobile=null, countryCallingCode=null,password="a12345")
解决思路
- 服务A和服务B的版本不一致,导致接口调用时参数传递出错。排查后不是该原因
- LoginReqDTO中的某些属性没有get和set方法。排查后不是该原因
- 经过与其他可以获取到的参数进行对比发现:该字段相较于其它字段较特殊的地方是子类和父类有相同的字段,去掉继承,发现字段能够顺利传递过去了。(
问题的根本点终于找到了)
@Data
public class LoginReqDTO extends BaseReq{
private String mobile; // 父类已存在,子类重复定义
private String countryCallingCode; // 父类已存在,子类重复定义
private String password;
......
}
@Data
public class BaseReq{
private String appKey;
private String mobile;
private String countryCallingCode;
}
解决办法
- 避免在子类中出现与父类同名的属性:比较简单,但是在一个大的团队中协同开发时,无法避免。
- 找一个没有BUG得HESSION版本或者自己动手改改代码重新打个包
相对简单的改法是:在出现同名时,如果子类中已经有了,那么父类中对应属性直接忽略,代码量很少,只需要加一个 continue 即可,但是这样改容易挖坑。
- 直接用Java原生的序列化方法(最笨的一个解决办法):
B obj = new B();
obj.setA(0);
// 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
// 反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
obj = (B) objectInputStream.readObject();
System.out.println(obj.a); // 0
问题虽然解决了,但是知其然而不知其所以然不是我们程序猿的风格,以下是原因分析过程,转载自牛人的一篇博客,希望对大家有帮助!^_^
下面是转载http://www.tuicool.com/articles/fERZbie
原因分析
在使用hessian将一个对象序列化、反序列化之后,发现原本有值的一个属性变成了NULL,观察发现在子类、父类有同名属性时会出现。
问题重现
- 构造测试类:
// 父类
class A implements Serializable {
public Integer a;
}
// 子类
class B extends A {
public Integer a;
}
- 序列化, 反序列化方法如下:
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(os);
ho.writeObject(obj);
return os.toByteArray();
}
public static Object deserialize(byte[] by) throws IOException {
ByteArrayInputStream is = new ByteArrayInputStream(by);
HessianInput hi = new HessianInput(is);
return hi.readObject();
}
- 测试代码:
@Test
public void testHessian() throws Exception {
B obj = new B();
obj.a = 0;
byte[] bytes = serialize(obj); // 序列化
obj = (B) deserialize(bytes); // 反序列化
System.out.println(obj.a); // null
}
原因分析
- 序列化的过程:
/*构造 UnsafeSerializer 时会遍历类及其父类的所有属性:*/
protected void introspect(Class<?> cl) {
ArrayList<Field> primitiveFields = new ArrayList<Field>();
ArrayList<Field> compoundFields = new ArrayList<Field>();
// 遍历所有父类
for (; cl != null; cl = cl.getSuperclass()) {
// 通过反射获取所有的属性
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
// 忽略transient和static的变量
if (Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()))
continue;
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<Field> fields = new ArrayList<Field>();
fields.addAll(primitiveFields);
fields.addAll(compoundFields);
_fields = new Field[fields.size()];
fields.toArray(_fields);
_fieldSerializers = new FieldSerializer[_fields.length];
// 构造序列化实现类
for (int i = 0; i < _fields.length; i++) {
_fieldSerializers[i] = getFieldSerializer(_fields[i]);
}
}
/*然后,遍历序列化各个属性字段来分别进行序列化:*/
protected void writeObject10(Object obj, AbstractHessianOutput out) throws IOException {
// 遍历属性
for (int i = 0; i < _fields.length; i++) {
Field field = _fields[i];
out.writeString(field.getName());
_fieldSerializers[i].serialize(out, obj); // 序列化
}
out.writeMapEnd();
}
- 反序列化过程:
public Object readMap(AbstractHessianInput in, Object obj) throws IOException {
// ....
// 循环读取序列化的内容。
while (!in.isEnd()) {
Object key = in.readObject();
// 相同名字的两个属性,拿到的是同一个desrializer。
FieldDeserializer deser = (FieldDeserializer) _fieldMap.get(key);
if (deser != null)
deser.deserialize(in, obj); // 在这里拿到value后设置到对应的属性中。
else
in.readObject();
}
// .....
}
- 序列化之后字节中对属性a有两个值,第一个非空,第二个空,那么在反序列化时会对a做两次赋值,第一次的结果为:a=0, 第二次的结果为a=null。
本文详细描述了在服务B通过RPC调用服务A的Dubbo接口时,遇到参数丢失的问题。现象表现为服务A接收到的参数与服务B传递的不一致。经过排查,问题根源在于子类和父类存在同名字段,导致Hessian序列化与反序列化过程中出现问题。解决办法包括避免子类与父类同名属性、更换或修改Hessian版本,以及使用Java原生序列化。文章提供了问题重现的测试代码和序列化、反序列化的分析过程。
600

被折叠的 条评论
为什么被折叠?



