问题分析
在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 finallyin = 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 finallyout = 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>