SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)

问题分析

在SpringBoot中使用 org.apache.commons.lang.SerializationUtils.clone 方法时,发现克隆出来的类强转对应类时发生类型不一致的错误,经过检测发现两个看似相同的类的类加载器不一致

场景

SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第1张图片

报错信息

java.lang.ClassCastException: com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint cannot be cast to com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint

检测信息

SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第2张图片

SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第3张图片

解决方法分析

既然发现类加载器不一致,那么需要找到类反序列化时的类加载器是如何指定得
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第4张图片

深入SerializationUtils.clone方法时发现内部是通过jdk的反序列化类ObjectInputStream将字节码转为对象得
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第5张图片

SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第6张图片

发现返回对象是由cons创建的,cons 是一个Constructor,那么需要判断cons是在哪里生成的,从而推断出类加载器的生成依据,而cons存在于ObjectStreamClass,进入ObjectStreamClass desc = readClassDesc(false);判断cons何时赋值
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第7张图片

发现执行完desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));这段代码时,cons有值,进入initNonProxy方法中
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第8张图片

发现最终指向Caches.localDescs一个map中
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第9张图片

localDescs是一个全局静态变量,所以需要知道在什么地方添加值的
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第10张图片

打断点debug发现在序列化的时候会new ObjectStreamClass(cl)并放在localDescs里,所以进入new ObjectStreamClass(cl)的方法里,找cons的来源
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第11张图片

发现cons是由Class cl生成,也就是说cons的类加载器信息是由Class cl的类加载器决定的,反向查找cl的加载
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第12张图片

发现cl的类加载信息由latestUserDefinedLoader(),查阅资料发现,latestUserDefinedLoader()会根据栈帧信息查找第一个非根类加载器或扩展类加载器,而SerializationUtils属于ApplicationClassLoader加载的范围,所以SerializationUtils.clone(point)返回的对象是由ApplicationClassLoader加载
SpringBoot SerializationUtils克隆(反序列化) 类加载器不一致问题(ClassCastException)_第13张图片

解决方案

方案一(推荐)

SerializationUtils.clone中的方法复制到项目中

public class TestClassLoaderController {

    private static PrePoint point = new PrePoint();


    @GetMapping("/testClassLoader")
    public AjaxResult testClassLoader() {
        PrePoint deserialize = (PrePoint) cloneObject(point);
        return null;

    }

    private static Object cloneObject(PrePoint point) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
        serialize(point, baos);
        byte[] bytes = baos.toByteArray();
        return deserialize(bytes);
    }

    public static Object deserialize(byte[] objectData) {
        if (objectData == null) {
            throw new IllegalArgumentException("The byte[] must not be null");
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
        return deserialize(bais);
    }

    public static Object deserialize(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The InputStream must not be null");
        }
        ObjectInputStream in = null;
        try {
            // stream closed in the finally
            in = new ObjectInputStream(inputStream);
            return in.readObject();

        } catch (ClassNotFoundException ex) {
            throw new SerializationException(ex);
        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                // ignore close exception
            }
        }
    }

    public static void serialize(Serializable obj, OutputStream outputStream) {
        if (outputStream == null) {
            throw new IllegalArgumentException("The OutputStream must not be null");
        }
        ObjectOutputStream out = null;
        try {
            // stream closed in the finally
            out = new ObjectOutputStream(outputStream);
            out.writeObject(obj);

        } catch (IOException ex) {
            throw new SerializationException(ex);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ex) {
                // ignore close exception
            }
        }
    }
}

方案二

关闭热加载

spring:
  devtools:
    restart:
      enabled: false

或者

@SpringBootApplication
public class SSMPApplication {
    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SSMPApplication.class);
    }
}

方案三

移除热加载的jar包

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-devtoolsartifactId>
  <optional>trueoptional> 
dependency>

你可能感兴趣的:(spring,boot,后端,java)