背景
由于项目是多人协作,服务调用使用的dubbo,在定义接口出入参的时候,我自己定义了一个基本入参xxxReq,包含了两个分页属性,后续有其他同事将此类继承了一个BaseReq。base中包含分页字段。
本地单元测试,接口都能根据分页数据的值查询出对应的数据,但是dubbo Consumer端查出的数据一直是走的默认数据。而且服务器上,根据分页条目的属性名查询日志,根本查不到日志输出,由于默认数据与数据库条目还是一致的,所以错误的以为触发了dubbo的缓存机制,并没有真正的将请求打到服务器上,也导致排查错误浪费了很多时间。
结果
后来停掉所有服务请求,仅保留出问题的请求,发送请求后,发现日志有打印输出,只不过分页字段的值都是null。所以将目光转向了出入参,发现了父子类的字段重复了,猜测是序列化问题,去除子类中的分页属性后,重新请求,Consumer正常,问题解决。
深入学习与分析
采用的是dubbo缺省协议,序列化方式是Hessian序列化,在反序列化时会出现父类的同名属性覆盖子类的同名属性问题,所以就变成null,解析不到了。
查看dubbo源码hessian下面,有很多Serializer。
以JavaSerializer为例学习一下序列化过程:
public JavaSerializer(Class cl, ClassLoader loader) {
this.introspectWriteReplace(cl, loader);
if (this._writeReplace != null) {
this._writeReplace.setAccessible(true);
}
ArrayList primitiveFields = new ArrayList();
ArrayList compoundFields;
int i;
for(compoundFields = new ArrayList(); cl != null; cl = cl.getSuperclass()) {
Field[] fields = cl.getDeclaredFields();
for(i = 0; i < fields.length; ++i) {
Field field = fields[i];
if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
if (!field.getType().isPrimitive() && (!field.getType().getName().startsWith("java.lang.") || field.getType().equals(Object.class))) {
compoundFields.add(field);
} else {
primitiveFields.add(field);
}
}
}
}
ArrayList fields = new ArrayList();
fields.addAll(primitiveFields);
fields.addAll(compoundFields);
this._fields = new Field[fields.size()];
fields.toArray(this._fields);
this._fieldSerializers = new JavaSerializer.FieldSerializer[this._fields.length];
for(i = 0; i < this._fields.length; ++i) {
this._fieldSerializers[i] = getFieldSerializer(this._fields[i].getType());
}
}
没怎么看懂,但是for循环的大致意思就是先遍历子类的属性进行存储,然后再进行父类的属性。
以JavaDeserializer为例学习一下序列化过程:
public JavaDeserializer(Class cl) {
this._type = cl;
this._fieldMap = this.getFieldMap(cl);
this._readResolve = this.getReadResolve(cl);
if (this._readResolve != null) {
this._readResolve.setAccessible(true);
}
Constructor[] constructors = cl.getDeclaredConstructors();
long bestCost = 9223372036854775807L;
for(int i = 0; i < constructors.length; ++i) {
Class[] param = constructors[i].getParameterTypes();
long cost = 0L;
for(int j = 0; j < param.length; ++j) {
cost = 4L * cost;
if (Object.class.equals(param[j])) {
++cost;
} else if (String.class.equals(param[j])) {
cost += 2L;
} else if (Integer.TYPE.equals(param[j])) {
cost += 3L;
} else if (Long.TYPE.equals(param[j])) {
cost += 4L;
} else if (param[j].isPrimitive()) {
cost += 5L;
} else {
cost += 6L;
}
}
if (cost < 0L || cost > 65536L) {
cost = 65536L;
}
cost += (long)param.length << 48;
if (cost < bestCost) {
this._constructor = constructors[i];
bestCost = cost;
}
}
if (this._constructor != null) {
this._constructor.setAccessible(true);
Class[] params = this._constructor.getParameterTypes();
this._constructorArgs = new Object[params.length];
for(int i = 0; i < params.length; ++i) {
this._constructorArgs[i] = getParamArg(params[i]);
}
}
}
后面一大堆很难懂,就看到一行this._fieldMap = this.getFieldMap(cl);这个是获取属性集合的一个方法,跟进去看一下:
protected HashMap getFieldMap(Class cl) {
HashMap fieldMap;
for(fieldMap = new HashMap(); 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()) && fieldMap.get(field.getName()) == null) {
try {
field.setAccessible(true);
} catch (Throwable var8) {
var8.printStackTrace();
}
Class type = field.getType();
Object deser;
if (String.class.equals(type)) {
deser = new JavaDeserializer.StringFieldDeserializer(field);
} else if (Byte.TYPE.equals(type)) {
deser = new JavaDeserializer.ByteFieldDeserializer(field);
} else if (Short.TYPE.equals(type)) {
deser = new JavaDeserializer.ShortFieldDeserializer(field);
} else if (Integer.TYPE.equals(type)) {
deser = new JavaDeserializer.IntFieldDeserializer(field);
} else if (Long.TYPE.equals(type)) {
deser = new JavaDeserializer.LongFieldDeserializer(field);
} else if (Float.TYPE.equals(type)) {
deser = new JavaDeserializer.FloatFieldDeserializer(field);
} else if (Double.TYPE.equals(type)) {
deser = new JavaDeserializer.DoubleFieldDeserializer(field);
} else if (Boolean.TYPE.equals(type)) {
deser = new JavaDeserializer.BooleanFieldDeserializer(field);
} else if (Date.class.equals(type)) {
deser = new JavaDeserializer.SqlDateFieldDeserializer(field);
} else if (Timestamp.class.equals(type)) {
deser = new JavaDeserializer.SqlTimestampFieldDeserializer(field);
} else if (Time.class.equals(type)) {
deser = new JavaDeserializer.SqlTimeFieldDeserializer(field);
} else {
deser = new JavaDeserializer.ObjectFieldDeserializer(field);
}
fieldMap.put(field.getName(), deser);
}
}
}
return fieldMap;
}
把序列化中的属性List放到HashMap中,HashMap: fieldMap.put(field.getName(), deser);由于map的key是field.getName(),由于父类后遍历,所以在最后把父类属性的null,传给了fieldMap。因此在反序列化时,将map读出来时出现了null值。
根据情况分析,仅代表个人愚见,有偏颇之处还望指出~