Hessian2序列化支持这个feature,让我们重构Dubbo接口更加容易
我们知道,dubbo RPC默认序列化方式是Hessian2。
🍀先看如下Hessian2序列化的测试代码。
// ------- MyDto ------- import lombok.Data; import lombok.experimental.Accessors; import java.io.Serializable; @Data @Accessors(chain = true) public class MyDto implements Serializable { private String id; private String name; private Integer num; } // ------ Hessian2序列化测试类 -------- import org.junit.Test; import java.io.IOException; import java.util.Base64; public class Hessian2SerializationTest { @Test public void testSerialize() throws IOException { MyDto myDto1 = new MyDto(); myDto1.setNum(1); byte[] bytes = Hessian2SerializationUtil.serialize(myDto1); /** * 通过{@link Base64#getEncoder()}把byte数组序列化成base64串。相应地,利用{@link Base64#getDecoder()}进行字符串的反序列化 */ String base64String = Base64.getEncoder().encodeToString(bytes); //new String(serialize, Charsets.UTF_8); System.out.println("base64串=" + base64String); MyDto myDto2 = (MyDto) Hessian2SerializationUtil.deSerialize(bytes); System.out.println(myDto2.getNum()); } @Test public void testDeserialize() throws IOException { String serializedBase64Text = "QxRkdWJib2RlbW8uZHRvLk15RHRvMZMHaW50ZWdlcgRuYW1lAmlkYOFOTg=="; byte[] bytes = Base64.getDecoder().decode(serializedBase64Text); //s.getBytes(Charsets.UTF_8); MyDto myDto = Hessian2SerializationUtil.deserialize(bytes, MyDto.class); System.out.println(myDto.getNum()); } }
🍀Hessian2序列化工具类(重要)

package dubbodemo.codec; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.common.serialize.ObjectInput; import org.apache.dubbo.common.serialize.ObjectOutput; import org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @Slf4j public class Hessian2SerializationUtil { /** * hessian2序列化,返回字节数组 * * @param obj 数据对象 * @return * @throws IOException */ public static byte[] serialize(Object obj) throws IOException { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(512);) { Hessian2Serialization hessian2Serialization = new Hessian2Serialization(); ObjectOutput output = hessian2Serialization.serialize(null, byteArrayOutputStream); output.writeObject(obj); output.flushBuffer(); return byteArrayOutputStream.toByteArray(); } } /** * hessian2反序列化,得到反序列对象 * * @param bytes 序列化时生成的字节数组 * @param <T> * @param objectClass 泛型T的class * @return * @throws IOException */ public static <T> T deserialize(byte[] bytes, Class<T> objectClass) throws IOException { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { Hessian2Serialization hessian2Serialization = new Hessian2Serialization(); try { ObjectInput objectInput = hessian2Serialization.deserialize(null, byteArrayInputStream); T object = objectInput.readObject(objectClass); log.debug("反序列化的对象=" + object.toString()); return object; } catch (ClassNotFoundException e) { e.printStackTrace(); } } return null; } }
hessian2序列化严格限定DTO的class类型,即用MyDto序列化,那么反序列化后只能转换成MyDto。上面deserialize方法debug日志样例 MyDto1(id=null, name=test name, num=112, price=null) ,如果反序列化试图转成其他类型,则会抛出ClassCastException,诸如 java.lang.ClassCastException: dubbodemo.dto.MyDto1 cannot be cast to dubbodemo.dto.MyDto2 。 Hessian2序列化并不限定DTO的类型,即用MyDto序列化,是可以反序列化成其他例如MyDto2的,它是根据dto里的field来做映射。
那么,怎么根据实体的field来做映射呢?严格来说,hessian2是根据field的名称和类型来匹配的,field类型不匹配则可能会出现,例如,序列化的field类型是String,反序列的同名field类型是Long,会出现 com.alibaba.com.caucho.hessian.io.HessianFieldException: com.emax.UserRequest.userId: expected long at 0x5 java.lang.String (11111) 。不过,这里面是有“黑科技”的。
我要说的正是这个。
- 1)在MyDto#num属性是Integer时,我们得到hessian2序列化结果,然后,修改MyDto#num为Long,前面的序列化结果可以正常反序列化。反之,MyDto#num先是Long并且取值在Integer数据范围内,然后修改成Integer,亦能正常反序列化。
- 2)上面结论1)也可以这样测试,序列化对象用MyDto,反序列化对象用MyDto1,MyDto#num是Integer,MyDto1#num是Integer。即Integer⇆Long,这是不影响序列化反序列化的。
- 2023年5月31日还验证到:Long→String,可以正常反序列化。----反之,String→Long是不行的,会抛上面的HessianFieldException。
这一点对我在做的重构工作有什么帮助呢?
我们的系统中,服务商数据的主属性--服务商id,在不同子系统里,这个id字段的类型不统一,varchar/int/bigint,程序pojo里对应的这个服务商id属性,String/Integer/Long 各不相同,这给我们的系统迭代(开发&维护)带来了许多麻烦。系统不断升级迭代,服务越来越多,重构的工作量以及风险就加剧,产生系统熵增。
这几天的北京,市民陆续“阳”起来,我们公司2/3的伙伴们都居家养病了。非常时期,一些产品需求就暂缓研发。我在上周不幸中招,经过难熬的一周,现已基本阳康,趁此空档窗口期,take action!决定动手重构一把。
其中,中台通道系统的channel-provider里有一个dubbo服务LevyMerchantRelationService,它提供的接口的参数是一个数据传输对象LevyMerchantRelationDTO,LevyMerchantRelationDTO的服务商id类型属性是Integer。从dubbo控制台来观察,LevyMerchantRelationService的消费者有14个应用共8个java工程。
那么,我要变更LevyMerchantRelationDTO里的服务商id类型为Long,作为消费方的这8个工程中涉及到这个属性的代码,都要跟着做调整。大好的消息是,有了上面hessian2序列化的这个优势,我们在上线的时候,就不用把14个消费者应用都同时上线,这将极大节省跨小组沟通和上线工作量,更重要的是,dubbo服务正常调用,丝毫不影响系统稳定。
这一点,增强了我这次重构的自信!
那么,我立马想到,如果dubbo接口方法的参数列表里有Integer的服务商id,例如 LevyDTO getByLevyId(Integer levyId); ,是不是也能直接改成Long而不影响dubbo消费者的调用呢?经验证,这个是行不通的!
🍀2023-4-19:com.alibaba.com.caucho.hessian.io.HessianFieldException: XxxDTO#xxfield: com.alibaba.com.caucho.hessian.io.StringValueDeserializer: unexpected object java.lang.String
昨晚例行上线后,今天早上生产发现一个bug。
什么bug呢?先看下面的dubbo接口
List<TaskDTO> listTask(TaskQuery query);
其中,TaskDTO里有个field叫price。这次上线时,我们一个小伙把price的数据类型由String改为BigDecimal了。
不巧的是,上线时漏掉了一个消费者,服务未重启。今天上午系统在使用时,出现了如下Exception。---->com.alibaba.com.caucho.hessian.io.HessianFieldException: com.emax.sale.comingparts.TaskDTO.price: com.alibaba.com.caucho.hessian.io.StringValueDeserializer: unexpected object java.lang.String (200)
此异常用文章开头的testcase很容易复现这个情况。 --->
--->如果不给TaskDTO#price赋值,即默认值为null,则修改其类型String→BigDecimal时,不会有影响。
--->当一旦给TaskDTO#price赋值了,那么在修改其类型String→BigDecimal后,反序列化就会抛出这个HessianFieldException-unexpected object java.lang.String。
如下是跑testcase关于这个HessianFieldException-unexpected object java.lang.String的详细异常堆栈。

com.alibaba.com.caucho.hessian.io.HessianFieldException: dubbodemo.dto.MyDto1.price: com.alibaba.com.caucho.hessian.io.StringValueDeserializer: unexpected object java.lang.String (200) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:167) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:410) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:276) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:203) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:532) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2820) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2743) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2278) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2717) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2278) at dubbodemo.consumer.Hessian2SerializationUtil.deserialize(Hessian2SerializationUtil.java:47) at dubbodemo.consumer.Hessian2SerializationTest.testDeSerialize(Hessian2SerializationTest.java:33) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) Caused by: com.alibaba.com.caucho.hessian.io.HessianProtocolException: com.alibaba.com.caucho.hessian.io.StringValueDeserializer: unexpected object java.lang.String (200) at com.alibaba.com.caucho.hessian.io.AbstractDeserializer.error(AbstractDeserializer.java:131) at com.alibaba.com.caucho.hessian.io.AbstractDeserializer.readObject(AbstractDeserializer.java:70) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2267) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406) ... 32 more
依旧是涉及到dubbo接口的一个重构。
项目代码里有2个作用相同的枚举,ChannelProviderTypeEnum 和 PlatformPartnerEnum。我们要统一用PlatformPartnerEnum。尽管我在ChannelProviderTypeEnum 上加了@Deprecated注解,依然存在有使用ChannelProviderTypeEnum 的代码。
最好的方式是一次做好。因此,我决定干掉 ChannelProviderTypeEnum 。
于是,自然又涉及到Hessian2序列化。
经用上面testcase测试, 真好。Hessian2序列化的这个特性,又给了我重构的动力。就是说,Hessian2在序列化枚举时,是根据枚举的name()匹配的,而不是枚举的类型。这就要求序列化前后的枚举里有相同name的枚举项。否则在反序列化对象时会报反射异常,如下是异常堆栈。
com.alibaba.com.caucho.hessian.io.HessianFieldException: dubbodemo.dto.MyDto1.providerTypeEnum: java.lang.reflect.InvocationTargetException at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:167) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:410) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:276) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:203) at com.alibaba.com.caucho.hessian.io.SerializerFactory.readObject(SerializerFactory.java:532) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2820) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2743) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2278) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2717) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2278) at dubbodemo.consumer.Hessian2SerializationUtil.deserialize(Hessian2SerializationUtil.java:47) at dubbodemo.consumer.Hessian2SerializationTest.testDeSerialize(Hessian2SerializationTest.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) Caused by: com.alibaba.com.caucho.hessian.io.IOExceptionWrapper: java.lang.reflect.InvocationTargetException at com.alibaba.com.caucho.hessian.io.EnumDeserializer.create(EnumDeserializer.java:133) at com.alibaba.com.caucho.hessian.io.EnumDeserializer.readObject(EnumDeserializer.java:118) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2818) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2145) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2118) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:2074) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:406) ... 32 more Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.alibaba.com.caucho.hessian.io.EnumDeserializer.create(EnumDeserializer.java:131) ... 39 more Caused by: java.lang.IllegalArgumentException: No enum constant dubbodemo.dto.PlatformPartnerEnum.BIG_AGENT at java.lang.Enum.valueOf(Enum.java:238) ... 44 more Process finished with exit code -1
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/16988123.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2020-12-16 时间格式里,sss与SSS的区别
2016-12-16 利用自定义异常来重构代码(▄︻┻┳═一不了解自定义异常者勿看)
2011-12-16 使用TransactionScope类定义代码块以参与事务
2011-12-16 c# 可空类型