今天踩的Dubbo的序列化对象的一个坑

Posted 圆圆的球

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了今天踩的Dubbo的序列化对象的一个坑相关的知识,希望对你有一定的参考价值。

Dubbo序列化对象的一个坑

​ --洱涷Zz


​ 今天在处理项目问题的时候遇到了Dubbo序列化对象的一个坑,记录一下:

​ 大致是同事在调用Dubbo rpc接口的时候发现返回的值和预期的返回值不符,预期是返回一个对象的集合,但是返回的集合里是hashMap,查看他们代码解决后是发现他们的消费者端对返回对象的路径定义与我们服务提供者端的路径不一致,导致返回的数据和预期不符。这个解决起来很简单,保持路径一致就可以。(最好就是将对象定义成公共的,可以很好的避免这些问题)

​ 但是为什么会出现这种情况呢?为什么会出现这个hashMap呢?

​ 一步一步理下来发现是消费端和服务端在进行类的序列化反序列化时出现的问题,首先Java中进行反序列化的类是JavaSerializer,这个类的构造方法里调用了这样的方法:getFieldMap,把里面本类和父类的所有方法放在fieldMap里,因为是hashMap,为了保证方法名不覆盖,这个里面就做了一个操作就是:

fieldMap.get(field.getName())!=null;

​ 在服务端进行反序列化时,会判断该路径下类名是否存在,如果存在,那么就对返回的集合对象进行循环赋值,如果不存在则会直接返回这个hashMap

​ 于此,问题的原因找到了,但是思考一下:

  • 既然是序列化的问题,那么如果是消费端和服务端的对象处于相同路径下,其序列化产生的uid不同会造成什么问题呢?
  • 如果是不同路径下,序列化的uid相同会产生什么问题?
  • 如果是相同路径下,对象还有父对象且其中部分属性相同,会有什么问题?
  • 如果是对象路径、uid、属性都相同但是没有实现序列化会有什么问题?

结论:

  • 由上述反序列化时所调用的方法可知,其在反序列化时是根据方法名和路径去进行匹配赋值的,只要路径相同,名称相同,无论uid是什么,都可以成功返回对象集合,

    • 继续引深,为什么呢?按照java应该是不可以实现的

    • 开始困扰了好久,查资料看到一句话,“hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式。”。后来就想,是不是因为序列化方式有关,会不会是Hessian序列化时并不会在意serialVersionUID的变化。为证实心中的疑虑。分别采用了Hessian和java两种序列化方式进行验证。

    • 这里有2个工程,一个发送端,一个接收端,采用SpringBoot启动,两个工程里面都分别载入了实体对象Person。用于测试对象serialVersionUID相同和不相同的情况。

    • 1.第一个工程,是传输对象接受端,这里反序列化。serializabletest-server。3个主类
      1.1.Person.java 传输对象。

      @ToString
      @Data
      public class Person implements Serializable {
      private static final long serialVersionUID = -1923645274767028479L;
      private String[] address;
      private String name;
      private int phone;
      }
      

      1.2. HessianController.java hessian反序列化接受类。通过一个Controller接受http提交过来的对象信息

      @Slf4j
      @RestController
      @RequestMapping("/hessian")
      public class HessianController {
          
          @RequestMapping(value = "/hello")
          public String helloWorld(HttpServletRequest request, HttpServletResponse response) throws IOException {
              log.info("hessian反序列化开始------------------------");
              ServletInputStream sis = request.getInputStream();
              Hessian2Input h2i = new Hessian2Input(sis);
              h2i.startMessage();
              Person person = (Person) h2i.readObject();
              h2i.completeMessage();
              h2i.close();
              sis.close();
              log.info("hessian反序列化结果"+person.toString());
              return person.toString();
              }
          
      }
      

      1.3 JavaController.java Java反序列化接受类,通过一个Controller接受http提交过来的对象信息

      @Slf4j
      @RestController
      @RequestMapping("/java")
      public class JavaController {
      
      @RequestMapping(value = "/seri")
      public String helloWorld(HttpServletRequest request, HttpServletResponse response) throws Exception {
      
          log.info("java反序列化开始------------------------");
          ServletInputStream sis = request.getInputStream();
          ObjectInputStream is = new ObjectInputStream(sis);
          Person person = (Person) is.readObject();
          log.info("java"+person.toString());
          return person.toString();
          
      	}
      }
      

      2、第二个工程,是传输对象发送端,这里进行对象序列化,并post发送请求。serializabletest-client。也是3个主类
      2.1 Person.java

      @ToString
      @Data
      public class Person implements Serializable {
      //通过变更serialVersionUID,分别测试与serializabletest-server中相同和不同的情况
      private static final long serialVersionUID = 6457272772L;
      
      private String[] address;
      
      private String name;
      
      private int phone;
      }
      

      2.2 HessianTest.java Hessian序列化对象 并发送

      public class HessianTest {
      public static String urlName = "http://localhost:8080/hessian/hello";
      
      public static void main(String[] args) throws Throwable {
      
          // 序列化
          ByteArrayOutputStream os = new ByteArrayOutputStream();
          Hessian2Output h2o = new Hessian2Output(os);
          h2o.startMessage();
          h2o.writeObject(getPerson());
          h2o.writeString("I am client.");
          h2o.completeMessage();
          h2o.close();
      
          byte[] buffer = os.toByteArray();
          os.close();
          ByteArrayEntity byteArrayEntity = new ByteArrayEntity(buffer,
                  ContentType.create("x-application/hessian", "UTF-8"));
      
          CloseableHttpClient client = HttpClients.createDefault();
          HttpPost post = new HttpPost(urlName);
          post.setEntity(byteArrayEntity);
          CloseableHttpResponse response = client.execute(post);
      
          System.out.println("response status:\\n"
                  + response.getStatusLine().getStatusCode());
          HttpEntity body = response.getEntity();
          System.out.println("body:"+body);
      }
      
      public static Person getPerson() {
          Person person = new Person();
          person.setAddress(new String[] { "Beijing", "TaiWan", "GuangZhou" });
          person.setName("Jack");
          person.setPhone(188888888);
          return person;
      }
      

      2.3 JavaTest.java java序列化并发送

      public class JavaTest {
      public static String urlName = "http://localhost:8080/java/seri";
      public static void main(String[] args) throws Throwable {   
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
          ObjectOutputStream os = new ObjectOutputStream(bos);
          os.writeObject(getPerson());
          byte[] buffer = bos.toByteArray();
          os.close();
          ByteArrayEntity byteArrayEntity = new ByteArrayEntity(buffer,
                  ContentType.create("x-java-serialized-object", "UTF-8"));
      
          CloseableHttpClient client = HttpClients.createDefault();
          HttpPost post = new HttpPost(urlName);
          post.setEntity(byteArrayEntity);
          CloseableHttpResponse response = client.execute(post);
      
          System.out.println("response status:\\n"
                  + response.getStatusLine().getStatusCode());
          HttpEntity body = response.getEntity();
          System.out.println("body:"+body);
      }
      
      public static Person getPerson() {
          Person person = new Person();
          person.setAddress(new String[] { "Beijing", "TaiWan", "GuangZhou" });
          person.setName("Jack");
          person.setPhone(188888888);
          return person;
      	}
      }
      

      根据测试结果我得到的结论是,在传输对象的serialVersionUID前后不一致时候,Hessian可以成功进行“反序列化”操作。但是java方式不能进行“反序列化”。

  • 和上面的答案类推可知,路径不同是获取不到类名的,从而无法判断,返回的就是个hashMap

  • 这个结果就需要介绍下反序列化时Hessian2Input这个类其中的方法readObjectInstance,它会取到本类和父类的所有方法放到一个数组fieldNames下,这些说完了说到这里面反序列化的方法JavaSerializerreadObject,是按fieldNames数组循环取值,在流里面挨个取出来,一直赋给本类的set方法,先是有值的,到父类时,取到的为空,就把本类的值覆盖了,就会出现返回值有空值,出现参数丢失的情况

  • 会直接报错,因为没有实现序列化。

以上是关于今天踩的Dubbo的序列化对象的一个坑的主要内容,如果未能解决你的问题,请参考以下文章

今天踩的Dubbo的序列化对象的一个坑

必读丨新手程序员最容易踩的“坑”,你踩过几个?

使用Ajax中get请求发送Token时踩的那些坑

SSM案例整合踩的一些坑

记录iOS踩的一些坑

Hololens开发初探踩的一些坑