记录使用dubbo,遇到的hessian序列化告警

问题

在使用dubbo时,线上出现了如下告警

WARN  com.alibaba.com.caucho.hessian.io.SerializerFactory -Hessian/Burlap: 
'com.qxz.third.model.dtk.DtkSuperCategoryResponse$SubCategory' 
is an unknown class in sun.misc.Launcher$AppClassLoader@681a9515:
java.lang.ClassNotFoundException:com.qxz.third.model.dtk.DtkSuperCategoryResponse$SubCategory

线上的调用链情况,A调用B,B调用C,”com.qxz.third.model.dtk.DtkSuperCategoryResponse$SubCategory“属于C服务暴露的类,在B服务通过spring的BeanUtils进行了转化,假如转化成了X对象,暴露给A,最终A得到X对象。
现在出现了此警告,但并没有影响服务运行,但奇怪的是A服务告警C服务中的类没有找到,但是A服务并没有直接调用C的服务。
此告警会不会有什么隐患,又是如何产生的呢?

问题复现,分析

对于这个问题,根据提示应该和hessian序列化有关系,但序列化之前我们通过BeanUtils进行了一次对象拷贝,初步判定这两个环节中可能出现了问题。

本地环境如下:

test-parent		父工程
	test-consumer			消费端
	test-provider			提供端
	test-provider-api		提供端暴露的接口

使用的类
org.apache.dubbo:dubbo:2.7.7
junit:junit:4.13
com.alibaba:fastjson:1.2.72

警告处找不到的类,都是集合类,我们也建一个包含集合类的pojo类,保持和线上的类一样
test-provider-api中建一个User2类,暴露给test-consumer使用

package com.anjz.provider.model;
import java.io.Serializable;
import java.util.List;
/**
 * @author shuai.ding
 * @date 2020年09月26日 8:59 下午
 */
public class User2 implements Serializable {
    private static final long serialVersionUID = -237774972958788586L;
    private Integer userId;
    private String userName;
    private List<Course> courseList;   
    //省略set、get方法

    public static class Course implements Serializable{
        private static final long serialVersionUID = -2407149932112578244L;
        private String courseName;
         //省略set、get方法
    }
}

test-provider中建一个User类,模拟从C模块中获取的类

package com.anjz.provider.thirdModel;
import java.io.Serializable;
import java.util.List;
/**
 * @author shuai.ding
 * @date 2020年09月26日 8:55 下午
 */
public class User implements Serializable {
    private static final long serialVersionUID = -237774972958788586L;
    private Integer userId;
    private String userName;
    private List<Course> courseList;
	//省略set、get方法
	
    public static class Course implements Serializable{
        private static final long serialVersionUID = -2407149932112578244L;
        private String courseName;
         //省略set、get方法
    }
}

test-provider中建一个UserService类,模拟通过服务获取数据,此处进行了一次pojo的拷贝,User -> User2

package com.anjz.provider.service;
import com.anjz.provider.model.User2;
import com.anjz.provider.thirdModel.User;
import org.springframework.beans.BeanUtils;
import java.util.Collections;
/**
 * @author shuai.ding
 * @date 2020年09月26日 9:26 下午
 */
public class UserService {
    public User2 getUserInfo(){
        //从其他提供方获取数据
        User user = new User();
        user.setUserId(1);
        user.setUserName("张三");

        User.Course course = new User.Course();
        course.setCourseName("数学");
        user.setCourseList(Collections.singletonList(course));

        User2 user2 = new User2();
        BeanUtils.copyProperties(user,user2);
        return user2;
    }
}

当test-consumer调用test-provider中的UserService.getUserInfo()方法时,test-provider端需要进行序列化,test-consumer端获取数据后,需要反序列化,此处使用dubbo的默认序列化hessian2。
此处将序列化的数据持久化到磁盘,来模拟网络的传输。

package com.anjz.provider;
import com.anjz.provider.model.User2;
import com.anjz.provider.service.UserService;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
 * @author shuai.ding
 * @date 2020年09月26日 9:32 下午
 */
public class Hessian2UtilsTest {
    @Test
    public void serialization() throws IOException {
        UserService userService = new UserService();
        User2 user2 = userService.getUserInfo();

        OutputStream os = new FileOutputStream(new File("/Users/dingshuai/Desktop/user2.txt"));
        Hessian2ObjectOutput hessian2ObjectOutput = new Hessian2ObjectOutput(os);
        hessian2ObjectOutput.writeObject(user2);
        hessian2ObjectOutput.flushBuffer();
    }
}

test-consumer端反序列化

package com.anjz.consumer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.anjz.provider.model.User2;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput;
import org.junit.Test;
import java.io.*;
/**
 * @author shuai.ding
 * @date 2020年09月26日 9:32 下午
 */
public class Hessian2UtilsTest {
    @Test
    public void deserialization() throws IOException {
        //hessian2反序列化
        InputStream is = new FileInputStream(new File("/Users/dingshuai/Desktop/user2.txt"));
        Hessian2ObjectInput hessian2ObjectInput = new Hessian2ObjectInput(is);
        Object object = hessian2ObjectInput.readObject();
        User2 user2 = (User2)object;
//        System.out.println(user2.getCourseList().get(0).getCourseName());
        System.out.println(JSON.toJSONString(user2, SerializerFeature.PrettyFormat));
    }
}

到此模拟完成一次调用,执行Hessian2UtilsTest.deserialization()方法,得到如下结果:

九月 26, 2020 10:52:00 下午 com.alibaba.com.caucho.hessian.io.SerializerFactory getDeserializer
警告: Hessian/Burlap: 'com.anjz.provider.thirdModel.User$Course' is an unknown class in sun.misc.Launcher$AppClassLoader@18b4aac2:
java.lang.ClassNotFoundException: com.anjz.provider.thirdModel.User$Course
{
	"courseList":[
		{
			"courseName":"数学"
		}
	],
	"userId":1,
	"userName":"张三"
}

这个结果和线上的告警是一样的,而且结果正常输出,我们来看看List中存放的到底是什么,将上述代码的注释的一行去掉,执行出现如下错误

java.lang.ClassCastException: 
java.util.HashMap cannot be cast to com.anjz.provider.model.User2$Course

这个错误是不是很奇怪,HashMap是从哪里来的?
那我们debug看一下反序列化后的user2
在这里插入图片描述
我们看到courseList容器中存在的数据是HashMap,看来Hessian2在进行反序列时,找不到类,直接反序列化成map类型了,同时给出告警,这也是最终结果没有问题的原因。(感兴趣,可以研究下dubbo中的hessian2做了哪些优化,改造)

为什么反序列化时,要去找com.anjz.provider.thirdModel.User$Course类,而不是找com.anjz.provider.Model.User2$Course类呢?还记得持久化到磁盘的类吧?我们来看一下这个文件
在这里插入图片描述
看到这,是不是觉得,最终引起这个问题的原因,肯定出在了BeanUtils这个拷贝上,恭喜你,你的感觉是对的。
这个我就直接说原因了,Spring的BeanUtils在进行类copy时,是一种浅拷贝,什么是浅拷贝呢,可能总结两点

1、对于基本类型,直接复制
2、对于引用类型,复制的是引用地址

spring中BeanUtils.copyProperties是通过set、get方法进行复制的(感兴趣的读者,可以去看看源码)
最后我们在看一下执行完BeanUtils.copyProperties后User2中的类的结构
在这里插入图片描述

总结

1、dubbo在进行反序列化时,如果找不到对象,会被反序列化成HashMap,给出类找不到的warn,具体的实现可研究com.alibaba.com.caucho.hessian.io.SerializerFactory类。
2、如果项目中出现上述告警,其实还是有隐患的,如再对提供者提供的数据进行处理,会报类型转化异常。
3、在项目中使用Spring中BeanUtils.copyProperties进行copy,特别要注意它的浅拷贝,对于类中集合类属性,建议遍历拷贝,再手动复制,对集合类手动进行一次深拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值