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

报错信息
java.lang.ClassCastException: com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint cannot be cast to com.tianqiauto.tis.pc.dingdanyupai.po.PrePoint
检测信息


解决方法分析
既然发现类加载器不一致,那么需要找到类反序列化时的类加载器是如何指定得

深入SerializationUtils.clone方法时发现内部是通过jdk的反序列化类ObjectInputStream将字节码转为对象得


发现返回对象是由cons创建的,cons 是一个Constructor,那么需要判断cons是在哪里生成的,从而推断出类加载器的生成依据,而cons存在于ObjectStreamClass,进入ObjectStreamClass desc = readClassDesc(false);判断cons何时赋值

发现执行完desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));这段代码时,cons有值,进入initNonProxy方法中

发现最终指向Caches.localDescs一个map中

localDescs是一个全局静态变量,所以需要知道在什么地方添加值的

打断点debug发现在序列化的时候会new ObjectStreamClass(cl)并放在localDescs里,所以进入new ObjectStreamClass(cl)的方法里,找cons的来源

发现cons是由Class<?> cl生成,也就是说cons的类加载器信息是由Class<?> cl的类加载器决定的,反向查找cl的加载

发现cl的类加载信息由latestUserDefinedLoader(),查阅资料发现,latestUserDefinedLoader()会根据栈帧信息查找第一个非根类加载器或扩展类加载器,而SerializationUtils属于ApplicationClassLoader加载的范围,所以SerializationUtils.clone(point)返回的对象是由ApplicationClassLoader加载

解决方案
方案一(推荐)
将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.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>


















