DateTime这个类不知道大家熟悉不,但是看到它的全限定名后估计你就知道它是谁了:cn.hutool.core.date.DateTime ,没错,它就存在于我们常用的hutool工具包里。

hutool包里有个处理日期时间的工具类:cn.hutool.core.date.DateUtil ,它提供了丰富的日期处理方法,但是它处理后的日期类型不是java.util.Date ,而是它内部封装的一个继承了java.util.Date的子类 cn.hutool.core.date.DateTime。

本次文章研讨的问题就是:为什么 DateTime对象 作为Dubbo远程调用的参数时,反序列化时不能正常解析出正确的日期?

Dubbo版本:2.7.15

举个例子来说明下

首先定义一个API接口

public interface IUserService {

// 根据注册日期范围筛选用户

List getUsersByRegisterTime(Date StartTime, Date endTime);

}

服务端的实现类如下

@DubboService

public class UserServiceImpl implements IUserService {

@Override

public List getUsersByRegisterTime(Date startTime, Date endTime) {

return findUserByRegisterTime(startTime, endTime);

}

private List findUserByRegisterTime(Date StartTime, Date endTime) {

List users = new ArrayList<>();

// …… 省略逻辑,从数据库获取用户列表

return users;

}

}

有一点要额外关注,我这里的 getUsersByRegisterTime 方法,参数类型的定义为 java.util.Date,意味着这个方法的提供者只想要日期类型的参数。

客户端的话,通过一个测试类来实现远程调用

@SpringBootTest

public class UserTest {

@DubboReference

IUserService userService;

@Test

public void rpcDateTest() {

// cn.hutool.core.date.DateTime

DateTime startTime = DateUtil.parse("2024-04-03 00:00:00");

DateTime endTime = DateUtil.parse("2024-04-04 23:59:59");

// java.util.Date

// Date startTime = new Date(2024, Calendar.APRIL,3,0,0,0);

// Date endTime = new Date(2024, Calendar.APRIL,4,23,59,59);

List users = userService.getUsersByRegisterTime(startTime, endTime);

System.out.println(users);

}

}

启动服务端,启动测试类,看看服务提供者收到的对象

2024-04-03 18:01:44 ,执行这段代码时,我电脑刚好也是这个时间。所以结论就是 DateTime远程传到服务端,经过反序列的步骤后,转成了Date对象,时间却变成了当前时间。

为了找到出现这个异常的原因,我翻看了一遍Dubbo的源码,最终锁定在com.alibaba.com.caucho.hessian.io.Hessian2Input 这个类上,这是Dubbo包里提供的一个类,由于源码跳来跳去的眼晕,所以我写了个main方法来复刻一个事故现场

public static void main(String[] args) throws IOException {

DateTime startTime = DateUtil.parse("2024-04-03 00:00:00");

ByteArrayOutputStream bos = new ByteArrayOutputStream();

Hessian2Output out = new Hessian2Output(bos);

out.writeObject(startTime);

out.close();

byte[] byteArray = bos.toByteArray();

ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);

Hessian2Input in = new Hessian2Input(bis);

Object obj = in.readObject();

System.out.println(obj.getClass());

System.out.println(obj);

}

输出结果

class cn.hutool.core.date.DateTime

2024-04-03 18:10:40

看,完美的复现了事故现场,究其原因,其实是Dubbo包下的Hessian2Input类没有对hutool包下的DateTime类提供合适的序列化支持,导致反序列化时先创建了一个DateTime对象(此时对象的时间为当前时间),但是却没有将时间相关的属性值(fastTime)给赋进去。

Debug详细过程如下

首先看序列化过程

断点打在writeOject方法

跟进去,由于dubbo包的 Hessian 中没有内置DateTime的专属系列化器,所以使用了默认的JavaSerializer(可见DateTime是个编外人员)

然后看writeObject的过程,即序列化的过程,进入到JavaSerializer的writeObject方法,下图就是序列化DateTime的几个步骤

首先 writeDefinition20(out) 方法将DateTime的四个属性值序列化到输出流中

然后 writeObjectBegin 方法将class的类定义写入输出流

最后 writeInstance 将对象的属性值也写入输出流

通过序列化的过程可以发现,Hessian2Output 仅仅将DateTime的四个属性值进行了序列化,并没有把从java.util.Date继承的 fastTime 属性进行序列化。

然后看反序列化的过程

首先将断点打在readObject方法上

跟进去,走到了Hessian2Input的readObject方法

通过readObjectDefinition 得到了要反序列化对象的全限定名与属性名称集合

然后进入Hessian2Input类的readObject方法,拿到上一步得到的类定义,调用 readObjectInstance 方法

readObjectInstance方法中,通过SerializerFactory拿到 JavaDeserializer 反序列化器,对 DateTime对象进行反序列化,注意看,这里反序列化时采用的DateTime构造器为无参构造器DateTime()

DateTime的构造函数如下

JavaDeserializer 的 readObject 方法中,先是通过构造器实例化了一个要反序列化的对象,这时 DateTime对象被创造出来,并且时间默认为当前时间(文章的前半部分是4/3号写的,由于鸽了几天,后半部分是4/9号写的,文章中的当前时间都是我写文章时的当前时间)

然后readObject(in, obj, fieldNames) 方法将DateTime的四个属性值给赋了进去,由于序列化的内容里没有fastTime相关内容,所以fastTime仍然是当前时间。

反序列化至此就结束了。

结论

DateTime在Dubbo中的序列化仅仅序列化了 mutable、firstDayOfWeek、timeZone、minimalDaysInFirstWeek 四个属性值,缺少了对当前时间的序列化内容,导致反序列化后的DateTime对象一直取的是当前时间。

java.util.Date对象的序列化

那么java.util.Date对象是怎么进行序列化与反序列化的呢,要知道Date类中对fastTime的属性定义可是加上了 transient 关键字的,意味着这个属性不会参与到序列化过程中

Dubbo的内置Hessian序列化是支持了Date对象的,序列化时采用 BasicSerializer 序列化器

BasicSerializer#writeObject中对Date对象的序列化支持如下

Date#getTime()方法中返回了fastTime的值

然后BasicSerializer就将这个fastTime的值序列化到了输出流中。

反序列化时,Hessian2Input#readObject 方法中对Date对象的支持如下

parseInt() * 60000L 读到的就是fastTime的值。

OK,到此为止,在Dubbo默认的序列化方式下,为什么java.util.Date对象能够正常被序列化与反序列化,而 cn.hutool.core.date.DateTime 却不能,这两个问题已经剖析明白了,如果你也在使用Dubbo的话,请多多注意这个问题,避免线上踩坑。

精彩内容

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: