Hessian2序列化支持这个feature,让我们重构Dubbo接口更加容易
  wTh0bPPZpNwc 2023年11月01日 37 0

我们知道,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;
    }
}
View Code

 

 

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
View Code

 

 

 



🍀2023-5-5

依旧是涉及到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

 

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  2Vtxr3XfwhHq   2024年05月17日   55   0   0 Java
  Tnh5bgG19sRf   2024年05月20日   113   0   0 Java
  8s1LUHPryisj   2024年05月17日   48   0   0 Java
  aRSRdgycpgWt   2024年05月17日   47   0   0 Java
wTh0bPPZpNwc